or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-editor-api.mdeditor-management.mdindex.mdmenu-components.mdvue-components.mdvue-renderers.md

menu-components.mddocs/

0

# Menu Components

1

2

Interactive menu components that float above or beside editor content, providing contextual editing tools and actions.

3

4

## Capabilities

5

6

### BubbleMenu Component

7

8

A floating menu that appears when text is selected, providing quick access to formatting options.

9

10

```typescript { .api }

11

/**

12

* Floating bubble menu that appears on text selection

13

* Uses Vue Teleport to render outside component tree

14

*/

15

const BubbleMenu: DefineComponent<{

16

/** Plugin identifier for registration */

17

pluginKey?: {

18

type: PropType<BubbleMenuPluginProps['pluginKey']>;

19

default: 'bubbleMenu';

20

};

21

22

/** Editor instance (required) */

23

editor: {

24

type: PropType<Editor>;

25

required: true;

26

};

27

28

/** Delay in milliseconds before updating position */

29

updateDelay?: {

30

type: PropType<number>;

31

default: undefined;

32

};

33

34

/** Delay in milliseconds before updating on resize */

35

resizeDelay?: {

36

type: PropType<number>;

37

default: undefined;

38

};

39

40

/** Additional plugin options */

41

options?: {

42

type: PropType<BubbleMenuPluginOptions>;

43

default: () => ({});

44

};

45

46

/** Custom function to determine when menu should show */

47

shouldShow?: {

48

type: PropType<(props: { editor: Editor; view: EditorView; state: EditorState; oldState?: EditorState; from: number; to: number; }) => boolean>;

49

default: null;

50

};

51

}>;

52

53

interface BubbleMenuPluginOptions {

54

/** Custom positioning element */

55

element?: HTMLElement;

56

/** Custom tippyjs options */

57

tippyOptions?: Record<string, any>;

58

/** Custom placement */

59

placement?: 'top' | 'bottom' | 'left' | 'right';

60

}

61

```

62

63

**Key Features:**

64

- Automatically positions near text selection

65

- Uses Vue Teleport to render in document body

66

- Customizable show/hide logic

67

- Configurable update and resize delays

68

- Integrates with @floating-ui/dom for positioning

69

70

**Usage Examples:**

71

72

```typescript

73

<template>

74

<div v-if="editor">

75

<BubbleMenu :editor="editor" :updateDelay="100">

76

<div class="bubble-menu">

77

<button

78

@click="editor.chain().focus().toggleBold().run()"

79

:class="{ 'is-active': editor.isActive('bold') }"

80

>

81

Bold

82

</button>

83

<button

84

@click="editor.chain().focus().toggleItalic().run()"

85

:class="{ 'is-active': editor.isActive('italic') }"

86

>

87

Italic

88

</button>

89

<button

90

@click="editor.chain().focus().toggleStrike().run()"

91

:class="{ 'is-active': editor.isActive('strike') }"

92

>

93

Strike

94

</button>

95

</div>

96

</BubbleMenu>

97

<EditorContent :editor="editor" />

98

</div>

99

</template>

100

101

<script setup>

102

import { useEditor, EditorContent, BubbleMenu } from "@tiptap/vue-3";

103

import StarterKit from "@tiptap/starter-kit";

104

105

const editor = useEditor({

106

content: '<p>Select some text to see the bubble menu!</p>',

107

extensions: [StarterKit],

108

});

109

</script>

110

111

<style>

112

.bubble-menu {

113

display: flex;

114

background: white;

115

box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), 0 10px 20px rgba(0, 0, 0, 0.1);

116

border-radius: 0.5rem;

117

overflow: hidden;

118

}

119

120

.bubble-menu button {

121

border: none;

122

background: none;

123

padding: 0.5rem;

124

cursor: pointer;

125

}

126

127

.bubble-menu button.is-active {

128

background: #e5e7eb;

129

}

130

</style>

131

```

132

133

**Custom shouldShow Logic:**

134

135

```typescript

136

<template>

137

<BubbleMenu

138

:editor="editor"

139

:shouldShow="customShouldShow"

140

>

141

<div class="custom-bubble-menu">

142

<!-- Menu content -->

143

</div>

144

</BubbleMenu>

145

</template>

146

147

<script setup>

148

const customShouldShow = ({ editor, view, state, from, to }) => {

149

// Only show for text selections (not node selections)

150

if (from === to) return false;

151

152

// Only show if selection contains bold text

153

return editor.isActive('bold');

154

};

155

</script>

156

```

157

158

### FloatingMenu Component

159

160

A floating menu that appears on empty lines, providing quick access to block-level formatting options.

161

162

```typescript { .api }

163

/**

164

* Floating menu that appears on empty lines

165

* Uses Vue Teleport to render outside component tree

166

*/

167

const FloatingMenu: DefineComponent<{

168

/** Plugin identifier for registration */

169

pluginKey?: {

170

type: null;

171

default: 'floatingMenu';

172

};

173

174

/** Editor instance (required) */

175

editor: {

176

type: PropType<Editor>;

177

required: true;

178

};

179

180

/** Additional plugin options */

181

options?: {

182

type: PropType<FloatingMenuPluginOptions>;

183

default: () => ({});

184

};

185

186

/** Custom function to determine when menu should show */

187

shouldShow?: {

188

type: PropType<(props: { editor: Editor; view: EditorView; state: EditorState; oldState?: EditorState; }) => boolean>;

189

default: null;

190

};

191

}>;

192

193

interface FloatingMenuPluginOptions {

194

/** Custom positioning element */

195

element?: HTMLElement;

196

/** Custom tippyjs options */

197

tippyOptions?: Record<string, any>;

198

/** Custom placement */

199

placement?: 'top' | 'bottom' | 'left' | 'right';

200

}

201

```

202

203

**Key Features:**

204

- Appears on empty paragraphs or lines

205

- Uses Vue Teleport for body-level rendering

206

- Customizable visibility conditions

207

- Integrates with @floating-ui/dom for positioning

208

209

**Usage Examples:**

210

211

```typescript

212

<template>

213

<div v-if="editor">

214

<FloatingMenu :editor="editor">

215

<div class="floating-menu">

216

<button @click="editor.chain().focus().toggleHeading({ level: 1 }).run()">

217

H1

218

</button>

219

<button @click="editor.chain().focus().toggleHeading({ level: 2 }).run()">

220

H2

221

</button>

222

<button @click="editor.chain().focus().toggleBulletList().run()">

223

Bullet List

224

</button>

225

<button @click="editor.chain().focus().toggleOrderedList().run()">

226

Ordered List

227

</button>

228

</div>

229

</FloatingMenu>

230

<EditorContent :editor="editor" />

231

</div>

232

</template>

233

234

<script setup>

235

import { useEditor, EditorContent, FloatingMenu } from "@tiptap/vue-3";

236

import StarterKit from "@tiptap/starter-kit";

237

238

const editor = useEditor({

239

content: '<p></p>',

240

extensions: [StarterKit],

241

});

242

</script>

243

244

<style>

245

.floating-menu {

246

display: flex;

247

background: white;

248

box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), 0 10px 20px rgba(0, 0, 0, 0.1);

249

border-radius: 0.5rem;

250

overflow: hidden;

251

}

252

253

.floating-menu button {

254

border: none;

255

background: none;

256

padding: 0.5rem;

257

cursor: pointer;

258

}

259

</style>

260

```

261

262

**Custom shouldShow for FloatingMenu:**

263

264

```typescript

265

<template>

266

<FloatingMenu

267

:editor="editor"

268

:shouldShow="customShouldShow"

269

>

270

<div class="floating-menu">

271

<!-- Menu content -->

272

</div>

273

</FloatingMenu>

274

</template>

275

276

<script setup>

277

const customShouldShow = ({ editor, view, state }) => {

278

const { selection } = state;

279

const { $anchor, empty } = selection;

280

281

// Only show on empty paragraphs

282

if (!empty) return false;

283

284

// Check if current node is a paragraph

285

if ($anchor.parent.type.name !== 'paragraph') return false;

286

287

// Only show if paragraph is empty

288

return $anchor.parent.textContent === '';

289

};

290

</script>

291

```

292

293

### Menu Styling and Positioning

294

295

**Advanced Styling:**

296

297

```typescript

298

<template>

299

<BubbleMenu

300

:editor="editor"

301

:options="{

302

placement: 'top',

303

tippyOptions: {

304

duration: 100,

305

animation: 'shift-away'

306

}

307

}"

308

>

309

<div class="menu-container">

310

<!-- Menu buttons -->

311

</div>

312

</BubbleMenu>

313

</template>

314

315

<style>

316

.menu-container {

317

background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);

318

border-radius: 8px;

319

padding: 8px;

320

box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);

321

}

322

</style>

323

```

324

325

**Multiple Menu Integration:**

326

327

```typescript

328

<template>

329

<div v-if="editor">

330

<!-- Bubble menu for text formatting -->

331

<BubbleMenu :editor="editor" pluginKey="textBubbleMenu">

332

<div class="text-menu">

333

<button @click="toggleBold">Bold</button>

334

<button @click="toggleItalic">Italic</button>

335

</div>

336

</BubbleMenu>

337

338

<!-- Floating menu for block elements -->

339

<FloatingMenu :editor="editor" pluginKey="blockFloatingMenu">

340

<div class="block-menu">

341

<button @click="addHeading">Heading</button>

342

<button @click="addList">List</button>

343

</div>

344

</FloatingMenu>

345

346

<EditorContent :editor="editor" />

347

</div>

348

</template>

349

```

350

351

## Types

352

353

```typescript { .api }

354

interface BubbleMenuPluginProps {

355

pluginKey: string | PluginKey;

356

editor: Editor;

357

element: HTMLElement;

358

updateDelay?: number;

359

resizeDelay?: number;

360

options?: Record<string, any>;

361

shouldShow?: ((props: {

362

editor: Editor;

363

view: EditorView;

364

state: EditorState;

365

oldState?: EditorState;

366

from: number;

367

to: number;

368

}) => boolean) | null;

369

}

370

371

interface FloatingMenuPluginProps {

372

pluginKey: string | PluginKey;

373

editor: Editor;

374

element: HTMLElement;

375

options?: Record<string, any>;

376

shouldShow?: ((props: {

377

editor: Editor;

378

view: EditorView;

379

state: EditorState;

380

oldState?: EditorState;

381

}) => boolean) | null;

382

}

383

```