or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

built-in-modifiers.mdcore-positioning.mdindex.mdvariants-tree-shaking.mdvirtual-elements.md

virtual-elements.mddocs/

0

# Virtual Elements

1

2

Support for positioning relative to virtual coordinates for context menus, mouse-following tooltips, and positioning relative to abstract points rather than DOM elements.

3

4

## Capabilities

5

6

### Virtual Element Interface

7

8

Definition for objects that can be used as reference elements without being actual DOM elements.

9

10

```javascript { .api }

11

/**

12

* Virtual element that can be positioned relative to

13

* Must implement getBoundingClientRect to define positioning reference

14

*/

15

interface VirtualElement {

16

/** Returns rectangle defining the virtual element's position and size */

17

getBoundingClientRect(): ClientRect | DOMRect;

18

/** Optional context element for boundary calculations */

19

contextElement?: Element;

20

}

21

22

interface ClientRect {

23

x: number;

24

y: number;

25

top: number;

26

left: number;

27

right: number;

28

bottom: number;

29

width: number;

30

height: number;

31

}

32

```

33

34

**Usage Examples:**

35

36

```javascript

37

import { createPopper } from '@popperjs/core';

38

39

// Mouse cursor virtual element

40

const cursorVirtualElement = {

41

getBoundingClientRect() {

42

return {

43

x: mouseX,

44

y: mouseY,

45

top: mouseY,

46

left: mouseX,

47

right: mouseX,

48

bottom: mouseY,

49

width: 0,

50

height: 0,

51

};

52

},

53

};

54

55

const popper = createPopper(cursorVirtualElement, tooltip, {

56

placement: 'bottom-start',

57

});

58

59

// Update position on mouse move

60

document.addEventListener('mousemove', (event) => {

61

mouseX = event.clientX;

62

mouseY = event.clientY;

63

popper.update();

64

});

65

```

66

67

### Mouse-Following Tooltips

68

69

Create tooltips that follow the mouse cursor.

70

71

```javascript { .api }

72

// No specific API - use VirtualElement with mouse coordinates

73

```

74

75

**Usage Examples:**

76

77

```javascript

78

import { createPopper } from '@popperjs/core';

79

80

let mouseX = 0;

81

let mouseY = 0;

82

83

// Virtual element that tracks mouse position

84

const mouseVirtualElement = {

85

getBoundingClientRect() {

86

return {

87

x: mouseX,

88

y: mouseY,

89

top: mouseY,

90

left: mouseX,

91

right: mouseX,

92

bottom: mouseY,

93

width: 0,

94

height: 0,

95

};

96

},

97

};

98

99

// Create popper with virtual element

100

const mousePopper = createPopper(mouseVirtualElement, tooltip, {

101

placement: 'bottom-start',

102

modifiers: [

103

{

104

name: 'offset',

105

options: {

106

offset: [10, 10], // Offset from cursor

107

},

108

},

109

],

110

});

111

112

// Track mouse movement

113

document.addEventListener('mousemove', (event) => {

114

mouseX = event.clientX;

115

mouseY = event.clientY;

116

117

// Update popper position

118

mousePopper.update();

119

});

120

121

// Show tooltip on hover

122

document.addEventListener('mouseenter', () => {

123

tooltip.style.visibility = 'visible';

124

});

125

126

document.addEventListener('mouseleave', () => {

127

tooltip.style.visibility = 'hidden';

128

});

129

```

130

131

### Context Menus

132

133

Position context menus relative to click coordinates.

134

135

```javascript { .api }

136

// No specific API - use VirtualElement with click coordinates

137

```

138

139

**Usage Examples:**

140

141

```javascript

142

import { createPopper } from '@popperjs/core';

143

144

// Context menu virtual element

145

function createContextMenuVirtualElement(x, y) {

146

return {

147

getBoundingClientRect() {

148

return {

149

x,

150

y,

151

top: y,

152

left: x,

153

right: x,

154

bottom: y,

155

width: 0,

156

height: 0,

157

};

158

},

159

};

160

}

161

162

// Show context menu on right-click

163

document.addEventListener('contextmenu', (event) => {

164

event.preventDefault();

165

166

const virtualElement = createContextMenuVirtualElement(

167

event.clientX,

168

event.clientY

169

);

170

171

const contextPopper = createPopper(virtualElement, contextMenu, {

172

placement: 'bottom-start',

173

modifiers: [

174

{

175

name: 'flip',

176

options: {

177

boundary: 'viewport',

178

},

179

},

180

{

181

name: 'preventOverflow',

182

options: {

183

boundary: 'viewport',

184

},

185

},

186

],

187

});

188

189

contextMenu.style.visibility = 'visible';

190

191

// Hide on click outside

192

const hideMenu = () => {

193

contextMenu.style.visibility = 'hidden';

194

contextPopper.destroy();

195

document.removeEventListener('click', hideMenu);

196

};

197

198

document.addEventListener('click', hideMenu);

199

});

200

```

201

202

### Selection-Based Positioning

203

204

Position tooltips relative to text selections or ranges.

205

206

```javascript { .api }

207

// No specific API - use VirtualElement with selection coordinates

208

```

209

210

**Usage Examples:**

211

212

```javascript

213

import { createPopper } from '@popperjs/core';

214

215

// Create virtual element from text selection

216

function createSelectionVirtualElement() {

217

const selection = window.getSelection();

218

if (!selection.rangeCount) return null;

219

220

const range = selection.getRangeAt(0);

221

const rect = range.getBoundingClientRect();

222

223

return {

224

getBoundingClientRect() {

225

return rect;

226

},

227

contextElement: range.commonAncestorContainer.parentElement,

228

};

229

}

230

231

// Show tooltip for selected text

232

document.addEventListener('selectionchange', () => {

233

const selection = window.getSelection();

234

235

if (selection.toString().length > 0) {

236

const virtualElement = createSelectionVirtualElement();

237

238

if (virtualElement) {

239

const selectionPopper = createPopper(virtualElement, selectionTooltip, {

240

placement: 'top',

241

modifiers: [

242

{

243

name: 'offset',

244

options: {

245

offset: [0, 8],

246

},

247

},

248

],

249

});

250

251

selectionTooltip.style.visibility = 'visible';

252

253

// Store reference for cleanup

254

window.currentSelectionPopper = selectionPopper;

255

}

256

} else {

257

// Hide tooltip when selection is cleared

258

if (window.currentSelectionPopper) {

259

selectionTooltip.style.visibility = 'hidden';

260

window.currentSelectionPopper.destroy();

261

window.currentSelectionPopper = null;

262

}

263

}

264

});

265

```

266

267

### Dynamic Virtual Elements

268

269

Virtual elements that change position or size over time.

270

271

```javascript { .api }

272

// No specific API - implement getBoundingClientRect with dynamic values

273

```

274

275

**Usage Examples:**

276

277

```javascript

278

import { createPopper } from '@popperjs/core';

279

280

// Animated virtual element

281

class AnimatedVirtualElement {

282

constructor(startX, startY, endX, endY, duration) {

283

this.startX = startX;

284

this.startY = startY;

285

this.endX = endX;

286

this.endY = endY;

287

this.duration = duration;

288

this.startTime = Date.now();

289

}

290

291

getBoundingClientRect() {

292

const elapsed = Date.now() - this.startTime;

293

const progress = Math.min(elapsed / this.duration, 1);

294

295

// Easing function

296

const eased = 1 - Math.pow(1 - progress, 3);

297

298

const x = this.startX + (this.endX - this.startX) * eased;

299

const y = this.startY + (this.endY - this.startY) * eased;

300

301

return {

302

x,

303

y,

304

top: y,

305

left: x,

306

right: x,

307

bottom: y,

308

width: 0,

309

height: 0,

310

};

311

}

312

}

313

314

// Create animated tooltip

315

const animatedVirtual = new AnimatedVirtualElement(100, 100, 300, 200, 1000);

316

const animatedPopper = createPopper(animatedVirtual, tooltip, {

317

placement: 'top',

318

});

319

320

// Update during animation

321

const animate = () => {

322

animatedPopper.update();

323

324

const elapsed = Date.now() - animatedVirtual.startTime;

325

if (elapsed < animatedVirtual.duration) {

326

requestAnimationFrame(animate);

327

}

328

};

329

330

animate();

331

```

332

333

### Context Elements

334

335

Using contextElement for proper boundary calculations with virtual elements.

336

337

```javascript { .api }

338

interface VirtualElement {

339

getBoundingClientRect(): ClientRect | DOMRect;

340

/** Element to use for scroll parent and boundary calculations */

341

contextElement?: Element;

342

}

343

```

344

345

**Usage Examples:**

346

347

```javascript

348

import { createPopper } from '@popperjs/core';

349

350

// Virtual element with context for proper scrolling behavior

351

const virtualWithContext = {

352

getBoundingClientRect() {

353

return {

354

x: 150,

355

y: 150,

356

top: 150,

357

left: 150,

358

right: 150,

359

bottom: 150,

360

width: 0,

361

height: 0,

362

};

363

},

364

// Use container for scroll parent detection

365

contextElement: document.querySelector('.scrollable-container'),

366

};

367

368

const popper = createPopper(virtualWithContext, tooltip, {

369

placement: 'bottom',

370

modifiers: [

371

{

372

name: 'preventOverflow',

373

options: {

374

boundary: 'clippingParents', // Will use contextElement's clipping parents

375

},

376

},

377

],

378

});

379

380

// The popper will now properly handle scrolling within .scrollable-container

381

```

382

383

### Error Handling

384

385

Common patterns for handling virtual element edge cases.

386

387

```javascript

388

// Validate virtual element before use

389

function createSafeVirtualElement(x, y, contextElement) {

390

// Ensure coordinates are valid numbers

391

if (typeof x !== 'number' || typeof y !== 'number' ||

392

!isFinite(x) || !isFinite(y)) {

393

throw new Error('Invalid coordinates for virtual element');

394

}

395

396

return {

397

getBoundingClientRect() {

398

return {

399

x,

400

y,

401

top: y,

402

left: x,

403

right: x,

404

bottom: y,

405

width: 0,

406

height: 0,

407

};

408

},

409

contextElement,

410

};

411

}

412

413

// Handle dynamic coordinate updates safely

414

function updateVirtualElementPopper(popper, x, y) {

415

if (popper && typeof x === 'number' && typeof y === 'number') {

416

// Update the virtual element's coordinates

417

// Then update popper position

418

popper.update();

419

}

420

}

421

422

// Clean up event listeners when destroying virtual element poppers

423

function cleanupVirtualPopper(popper) {

424

if (popper) {

425

popper.destroy();

426

// Remove any associated event listeners

427

document.removeEventListener('mousemove', updateHandler);

428

document.removeEventListener('click', clickHandler);

429

}

430

}

431

```