or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

tessl/npm-milkdown--plugin-tooltip

Tooltip plugin system for the Milkdown markdown editor with configurable positioning and display logic

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/@milkdown/plugin-tooltip@7.15.x

To install, run

npx @tessl/cli install tessl/npm-milkdown--plugin-tooltip@7.15.0

0

# Milkdown Plugin Tooltip

1

2

Milkdown Plugin Tooltip provides a tooltip plugin system for the Milkdown markdown editor framework. It offers a factory function for creating customizable tooltips with identifiers, and a provider class that handles intelligent positioning and display logic using floating-ui.

3

4

## Package Information

5

6

- **Package Name**: @milkdown/plugin-tooltip

7

- **Package Type**: npm

8

- **Language**: TypeScript

9

- **Installation**: `npm install @milkdown/plugin-tooltip`

10

11

## Core Imports

12

13

```typescript

14

import { tooltipFactory, TooltipProvider, type TooltipProviderOptions } from "@milkdown/plugin-tooltip";

15

import { $ctx, $prose } from "@milkdown/utils";

16

import type { SliceType } from "@milkdown/ctx";

17

```

18

19

For CommonJS:

20

21

```javascript

22

const { tooltipFactory, TooltipProvider } = require("@milkdown/plugin-tooltip");

23

```

24

25

## Basic Usage

26

27

```typescript

28

import { tooltipFactory, TooltipProvider } from "@milkdown/plugin-tooltip";

29

import { Editor } from "@milkdown/core";

30

31

// Create a tooltip plugin with unique identifier

32

const [tooltipSpec, tooltipPlugin] = tooltipFactory("myTooltip");

33

34

// Create tooltip content element

35

const tooltipElement = document.createElement("div");

36

tooltipElement.innerHTML = "Custom tooltip content";

37

tooltipElement.className = "my-tooltip";

38

39

// Configure tooltip provider

40

const tooltipProvider = new TooltipProvider({

41

content: tooltipElement,

42

debounce: 300,

43

shouldShow: (view) => !view.state.selection.empty,

44

offset: { mainAxis: 10, crossAxis: 0 },

45

});

46

47

// Set up tooltip plugin with provider

48

tooltipSpec(ctx => ({

49

view: (view) => ({

50

update: (view, prevState) => {

51

tooltipProvider.update(view, prevState);

52

},

53

destroy: () => {

54

tooltipProvider.destroy();

55

}

56

})

57

}));

58

59

// Use in editor

60

const editor = Editor.make()

61

.config((ctx) => {

62

ctx.set(tooltipSpec.key, tooltipSpec);

63

})

64

.use([tooltipPlugin]);

65

```

66

67

## Architecture

68

69

The plugin follows Milkdown's plugin architecture with two core components:

70

71

- **Tooltip Factory**: Creates plugin instances with unique identifiers for integration with ProseMirror's plugin system

72

- **Tooltip Provider**: Manages tooltip lifecycle, positioning, and display logic using floating-ui

73

- **Positioning System**: Intelligent positioning with flip, offset, and shift middleware for optimal placement

74

- **Debounced Updates**: Performance optimization through configurable update throttling

75

76

## Capabilities

77

78

### Tooltip Plugin Factory

79

80

Creates a tooltip plugin with a unique identifier for integration with Milkdown's plugin system.

81

82

```typescript { .api }

83

/**

84

* Create a tooltip plugin with a unique id.

85

* @param id - Unique string identifier for the tooltip plugin

86

* @returns Tuple containing context specification and prose plugin with additional properties

87

*/

88

function tooltipFactory<Id extends string, State = any>(id: Id): TooltipPlugin<Id, State>;

89

90

type TooltipSpecId<Id extends string> = `${Id}_TOOLTIP_SPEC`;

91

92

type TooltipPlugin<Id extends string, State = any> = [

93

$Ctx<PluginSpec<State>, TooltipSpecId<Id>>,

94

$Prose,

95

] & {

96

key: SliceType<PluginSpec<State>, TooltipSpecId<Id>>;

97

pluginKey: $Prose['key'];

98

};

99

```

100

101

### Tooltip Provider

102

103

Manages tooltip positioning, display logic, and lifecycle using floating-ui for intelligent positioning.

104

105

```typescript { .api }

106

/**

107

* A provider for creating and managing tooltips with intelligent positioning

108

*/

109

class TooltipProvider {

110

/** The root element of the tooltip */

111

element: HTMLElement;

112

113

/** Callback executed when tooltip is shown */

114

onShow: () => void;

115

116

/** Callback executed when tooltip is hidden */

117

onHide: () => void;

118

119

constructor(options: TooltipProviderOptions);

120

121

/**

122

* Update provider state by editor view

123

* @param view - Current editor view

124

* @param prevState - Previous editor state for comparison

125

*/

126

update(view: EditorView, prevState?: EditorState): void;

127

128

/**

129

* Show the tooltip, optionally positioned relative to a virtual element

130

* @param virtualElement - Optional element to position tooltip relative to

131

*/

132

show(virtualElement?: VirtualElement): void;

133

134

/**

135

* Hide the tooltip

136

*/

137

hide(): void;

138

139

/**

140

* Destroy the tooltip and cancel pending updates

141

*/

142

destroy(): void;

143

}

144

```

145

146

### Tooltip Provider Configuration

147

148

Configuration options for customizing tooltip behavior and positioning.

149

150

```typescript { .api }

151

interface TooltipProviderOptions {

152

/** The tooltip content */

153

content: HTMLElement;

154

155

/** The debounce time for updating tooltip, 200ms by default */

156

debounce?: number;

157

158

/** Function to determine whether the tooltip should be shown */

159

shouldShow?: (view: EditorView, prevState?: EditorState) => boolean;

160

161

/** The offset to get the block. Default is 0 */

162

offset?: OffsetOptions;

163

164

/** The amount to shift options the block by */

165

shift?: ShiftOptions;

166

167

/** Other middlewares for floating ui. This will be added after the internal middlewares */

168

middleware?: Middleware[];

169

170

/** Options for floating ui. If you pass `middleware` or `placement`, it will override the internal settings */

171

floatingUIOptions?: Partial<ComputePositionConfig>;

172

173

/** The root element that the tooltip will be appended to */

174

root?: HTMLElement;

175

}

176

```

177

178

## Types

179

180

```typescript { .api }

181

// Milkdown core types used in API

182

interface $Ctx<T, N extends string> {

183

key: SliceType<T, N>;

184

meta: { package: string; displayName: string };

185

}

186

187

interface $Prose {

188

key: Symbol;

189

meta: { package: string; displayName: string };

190

}

191

192

interface SliceType<T, N extends string> {

193

id: symbol;

194

name: N;

195

}

196

197

interface PluginSpec<State = any> {

198

props?: any;

199

state?: any;

200

key?: any;

201

view?: (view: EditorView) => {

202

update?: (view: EditorView, prevState: EditorState) => void;

203

destroy?: () => void;

204

};

205

}

206

207

// Floating-ui types used in configuration

208

interface OffsetOptions {

209

mainAxis?: number;

210

crossAxis?: number;

211

alignmentAxis?: number | null;

212

}

213

214

interface ShiftOptions {

215

mainAxis?: boolean;

216

crossAxis?: boolean;

217

limiter?: {

218

fn: (state: MiddlewareState) => Coords;

219

options?: any;

220

};

221

}

222

223

interface VirtualElement {

224

getBoundingClientRect(): ClientRect | DOMRect;

225

contextElement?: Element;

226

}

227

228

interface Middleware {

229

name: string;

230

options?: any;

231

fn: (state: MiddlewareState) => Coords | Promise<Coords>;

232

}

233

234

interface ComputePositionConfig {

235

placement?: Placement;

236

strategy?: Strategy;

237

middleware?: Array<Middleware | null | undefined | false>;

238

platform?: Platform;

239

}

240

241

// ProseMirror types used in API

242

interface EditorView {

243

state: EditorState;

244

dom: HTMLElement;

245

hasFocus(): boolean;

246

editable: boolean;

247

composing: boolean;

248

}

249

250

interface EditorState {

251

doc: Node;

252

selection: Selection;

253

}

254

```

255

256

## Usage Examples

257

258

### Basic Tooltip with Text Selection

259

260

```typescript

261

import { tooltipFactory, TooltipProvider } from "@milkdown/plugin-tooltip";

262

263

// Create tooltip plugin

264

const [tooltipSpec, tooltipPlugin] = tooltipFactory("selectionTooltip");

265

266

// Create tooltip content

267

const content = document.createElement("div");

268

content.innerHTML = "<button>Bold</button><button>Italic</button>";

269

content.className = "selection-tooltip";

270

271

// Configure provider to show on text selection

272

const provider = new TooltipProvider({

273

content,

274

shouldShow: (view) => !view.state.selection.empty, // Show when text is selected

275

offset: { mainAxis: 8 },

276

shift: { crossAxis: true },

277

});

278

279

// Set up plugin specification

280

tooltipSpec(ctx => ({

281

view: () => ({

282

update: provider.update,

283

destroy: provider.destroy,

284

})

285

}));

286

```

287

288

### Custom Positioning with Middleware

289

290

```typescript

291

import { flip, hide } from "@floating-ui/dom";

292

293

const provider = new TooltipProvider({

294

content: tooltipElement,

295

middleware: [

296

hide(), // Hide tooltip when reference is not visible

297

],

298

floatingUIOptions: {

299

placement: "bottom-start",

300

strategy: "fixed",

301

},

302

debounce: 100, // Faster updates

303

});

304

```

305

306

### Tooltip with Custom Show/Hide Logic

307

308

```typescript

309

const provider = new TooltipProvider({

310

content: tooltipElement,

311

shouldShow: (view, prevState) => {

312

const { selection } = view.state;

313

314

// Only show for text selections longer than 5 characters

315

if (selection.empty) return false;

316

317

const selectedText = view.state.doc.textBetween(

318

selection.from,

319

selection.to

320

);

321

322

return selectedText.length > 5;

323

},

324

});

325

326

// Add event handlers

327

provider.onShow = () => {

328

console.log("Tooltip shown");

329

provider.element.style.opacity = "1";

330

};

331

332

provider.onHide = () => {

333

console.log("Tooltip hidden");

334

provider.element.style.opacity = "0";

335

};

336

```

337

338

### Multiple Tooltips with Different Behaviors

339

340

```typescript

341

// Create multiple tooltip plugins with unique identifiers

342

const [formatTooltipSpec, formatTooltipPlugin] = tooltipFactory("formatting");

343

const [linkTooltipSpec, linkTooltipPlugin] = tooltipFactory("linkPreview");

344

345

// Configure different providers

346

const formatProvider = new TooltipProvider({

347

content: formatToolbarElement,

348

shouldShow: (view) => !view.state.selection.empty,

349

offset: { mainAxis: 10 },

350

});

351

352

const linkProvider = new TooltipProvider({

353

content: linkPreviewElement,

354

shouldShow: (view) => {

355

// Show when hovering over links

356

const { $head } = view.state.selection;

357

return $head.parent.type.name === "link";

358

},

359

debounce: 500,

360

});

361

```