or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

collections.mddesign-tokens.mddom-aria.mddrag-drop.mdevents.mdindex.mdinput-handling.mdlabelable.mdrefs.mdselection.mdstyling.md

events.mddocs/

0

# Event System

1

2

Custom event system with propagation control, pointer type awareness, and comprehensive interaction support including press, hover, focus, keyboard, and move events.

3

4

## Capabilities

5

6

### Base Event System

7

8

Enhanced event types with propagation control and pointer type awareness.

9

10

```typescript { .api }

11

/**

12

* Base event type with propagation control

13

* @template T The underlying synthetic event type

14

*/

15

type BaseEvent<T extends SyntheticEvent> = T & {

16

/** @deprecated Use continuePropagation */

17

stopPropagation(): void;

18

/** Allow event to continue propagating to parent elements */

19

continuePropagation(): void;

20

};

21

22

/** Enhanced keyboard event with propagation control */

23

type KeyboardEvent = BaseEvent<ReactKeyboardEvent<any>>;

24

25

/** Pointer interaction types */

26

type PointerType = "mouse" | "pen" | "touch" | "keyboard" | "virtual";

27

```

28

29

### Press Events

30

31

Press events provide a unified interaction model across different input methods.

32

33

```typescript { .api }

34

/**

35

* Press event details

36

*/

37

interface PressEvent {

38

/** The type of press event being fired */

39

type: "pressstart" | "pressend" | "pressup" | "press";

40

/** The pointer type that triggered the press event */

41

pointerType: PointerType;

42

/** The target element of the press event */

43

target: Element;

44

/** Whether the shift keyboard modifier was held during the press event */

45

shiftKey: boolean;

46

/** Whether the ctrl keyboard modifier was held during the press event */

47

ctrlKey: boolean;

48

/** Whether the meta keyboard modifier was held during the press event */

49

metaKey: boolean;

50

/** Whether the alt keyboard modifier was held during the press event */

51

altKey: boolean;

52

/** X position relative to the target */

53

x: number;

54

/** Y position relative to the target */

55

y: number;

56

/**

57

* By default, press events stop propagation to parent elements.

58

* In cases where a handler decides not to handle a specific event,

59

* it can call continuePropagation() to allow a parent to handle it.

60

*/

61

continuePropagation(): void;

62

}

63

64

/**

65

* Long press event (extends PressEvent but omits type and continuePropagation)

66

*/

67

interface LongPressEvent extends Omit<PressEvent, "type" | "continuePropagation"> {

68

/** The type of long press event being fired */

69

type: "longpressstart" | "longpressend" | "longpress";

70

}

71

72

/**

73

* Press event handlers

74

*/

75

interface PressEvents {

76

/** Handler that is called when the press is released over the target */

77

onPress?: (e: PressEvent) => void;

78

/** Handler that is called when a press interaction starts */

79

onPressStart?: (e: PressEvent) => void;

80

/**

81

* Handler that is called when a press interaction ends, either

82

* over the target or when the pointer leaves the target

83

*/

84

onPressEnd?: (e: PressEvent) => void;

85

/** Handler that is called when the press state changes */

86

onPressChange?: (isPressed: boolean) => void;

87

/**

88

* Handler that is called when a press is released over the target, regardless of

89

* whether it started on the target or not

90

*/

91

onPressUp?: (e: PressEvent) => void;

92

/**

93

* Not recommended – use onPress instead. onClick is an alias for onPress

94

* provided for compatibility with other libraries. onPress provides

95

* additional event details for non-mouse interactions.

96

*/

97

onClick?: (e: MouseEvent<FocusableElement>) => void;

98

}

99

```

100

101

### Hover Events

102

103

Hover events for mouse and pen interactions.

104

105

```typescript { .api }

106

/**

107

* Hover event details

108

*/

109

interface HoverEvent {

110

/** The type of hover event being fired */

111

type: "hoverstart" | "hoverend";

112

/** The pointer type that triggered the hover event */

113

pointerType: "mouse" | "pen";

114

/** The target element of the hover event */

115

target: HTMLElement;

116

}

117

118

/**

119

* Hover event handlers

120

*/

121

interface HoverEvents {

122

/** Handler that is called when a hover interaction starts */

123

onHoverStart?: (e: HoverEvent) => void;

124

/** Handler that is called when a hover interaction ends */

125

onHoverEnd?: (e: HoverEvent) => void;

126

/** Handler that is called when the hover state changes */

127

onHoverChange?: (isHovering: boolean) => void;

128

}

129

```

130

131

### Focus Events

132

133

Focus event handlers with focus state tracking.

134

135

```typescript { .api }

136

/**

137

* Focus event handlers

138

* @template Target The type of the target element

139

*/

140

interface FocusEvents<Target = Element> {

141

/** Handler that is called when the element receives focus */

142

onFocus?: (e: FocusEvent<Target>) => void;

143

/** Handler that is called when the element loses focus */

144

onBlur?: (e: FocusEvent<Target>) => void;

145

/** Handler that is called when the element's focus status changes */

146

onFocusChange?: (isFocused: boolean) => void;

147

}

148

149

/**

150

* Properties for focusable elements

151

* @template Target The type of the target element

152

*/

153

interface FocusableProps<Target = Element> extends FocusEvents<Target>, KeyboardEvents {

154

/** Whether the element should receive focus on render */

155

autoFocus?: boolean;

156

}

157

```

158

159

### Keyboard Events

160

161

Keyboard event handlers.

162

163

```typescript { .api }

164

/**

165

* Keyboard event handlers

166

*/

167

interface KeyboardEvents {

168

/** Handler that is called when a key is pressed */

169

onKeyDown?: (e: KeyboardEvent) => void;

170

/** Handler that is called when a key is released */

171

onKeyUp?: (e: KeyboardEvent) => void;

172

}

173

```

174

175

### Move Events

176

177

Move events for drag-like interactions without drag and drop.

178

179

```typescript { .api }

180

/**

181

* Base properties for move events

182

*/

183

interface BaseMoveEvent {

184

/** The pointer type that triggered the move event */

185

pointerType: PointerType;

186

/** Whether the shift keyboard modifier was held during the move event */

187

shiftKey: boolean;

188

/** Whether the ctrl keyboard modifier was held during the move event */

189

ctrlKey: boolean;

190

/** Whether the meta keyboard modifier was held during the move event */

191

metaKey: boolean;

192

/** Whether the alt keyboard modifier was held during the move event */

193

altKey: boolean;

194

}

195

196

/**

197

* Move start event

198

*/

199

interface MoveStartEvent extends BaseMoveEvent {

200

/** The type of move event being fired */

201

type: "movestart";

202

}

203

204

/**

205

* Move event with delta information

206

*/

207

interface MoveMoveEvent extends BaseMoveEvent {

208

/** The type of move event being fired */

209

type: "move";

210

/** The amount moved in the X direction since the last event */

211

deltaX: number;

212

/** The amount moved in the Y direction since the last event */

213

deltaY: number;

214

}

215

216

/**

217

* Move end event

218

*/

219

interface MoveEndEvent extends BaseMoveEvent {

220

/** The type of move event being fired */

221

type: "moveend";

222

}

223

224

/** Union of all move event types */

225

type MoveEvent = MoveStartEvent | MoveMoveEvent | MoveEndEvent;

226

227

/**

228

* Move event handlers

229

*/

230

interface MoveEvents {

231

/** Handler that is called when a move interaction starts */

232

onMoveStart?: (e: MoveStartEvent) => void;

233

/** Handler that is called when the element is moved */

234

onMove?: (e: MoveMoveEvent) => void;

235

/** Handler that is called when a move interaction ends */

236

onMoveEnd?: (e: MoveEndEvent) => void;

237

}

238

```

239

240

### Scroll Events

241

242

Scroll event support with delta information.

243

244

```typescript { .api }

245

/**

246

* Scroll event details

247

*/

248

interface ScrollEvent {

249

/** The amount moved in the X direction since the last event */

250

deltaX: number;

251

/** The amount moved in the Y direction since the last event */

252

deltaY: number;

253

}

254

255

/**

256

* Scroll event handlers

257

*/

258

interface ScrollEvents {

259

/** Handler that is called when the scroll wheel moves */

260

onScroll?: (e: ScrollEvent) => void;

261

}

262

```

263

264

**Usage Examples:**

265

266

```typescript

267

import {

268

PressEvents,

269

HoverEvents,

270

FocusableProps,

271

MoveEvents,

272

PressEvent,

273

HoverEvent,

274

KeyboardEvent

275

} from "@react-types/shared";

276

277

// Interactive button with press and hover support

278

interface InteractiveButtonProps extends PressEvents, HoverEvents, FocusableProps {

279

children: React.ReactNode;

280

isDisabled?: boolean;

281

}

282

283

function InteractiveButton({

284

children,

285

isDisabled,

286

onPress,

287

onPressStart,

288

onPressEnd,

289

onHoverStart,

290

onHoverEnd,

291

onFocus,

292

onBlur,

293

onKeyDown,

294

autoFocus

295

}: InteractiveButtonProps) {

296

const [isPressed, setIsPressed] = useState(false);

297

const [isHovered, setIsHovered] = useState(false);

298

const [isFocused, setIsFocused] = useState(false);

299

300

const handlePress = (e: PressEvent) => {

301

if (isDisabled) return;

302

console.log(`Pressed with ${e.pointerType} at (${e.x}, ${e.y})`);

303

onPress?.(e);

304

};

305

306

const handlePressStart = (e: PressEvent) => {

307

if (isDisabled) return;

308

setIsPressed(true);

309

onPressStart?.(e);

310

};

311

312

const handlePressEnd = (e: PressEvent) => {

313

setIsPressed(false);

314

onPressEnd?.(e);

315

};

316

317

const handleHoverStart = (e: HoverEvent) => {

318

if (isDisabled) return;

319

setIsHovered(true);

320

onHoverStart?.(e);

321

};

322

323

const handleHoverEnd = (e: HoverEvent) => {

324

setIsHovered(false);

325

onHoverEnd?.(e);

326

};

327

328

const handleKeyDown = (e: KeyboardEvent) => {

329

if (isDisabled) return;

330

if (e.key === "Enter" || e.key === " ") {

331

// Simulate press event for keyboard interaction

332

handlePress({

333

type: "press",

334

pointerType: "keyboard",

335

target: e.target as Element,

336

shiftKey: e.shiftKey,

337

ctrlKey: e.ctrlKey,

338

metaKey: e.metaKey,

339

altKey: e.altKey,

340

x: 0,

341

y: 0,

342

continuePropagation: () => {}

343

});

344

}

345

onKeyDown?.(e);

346

};

347

348

return (

349

<button

350

disabled={isDisabled}

351

autoFocus={autoFocus}

352

onMouseDown={(e) => handlePressStart({

353

type: "pressstart",

354

pointerType: "mouse",

355

target: e.target as Element,

356

shiftKey: e.shiftKey,

357

ctrlKey: e.ctrlKey,

358

metaKey: e.metaKey,

359

altKey: e.altKey,

360

x: e.clientX,

361

y: e.clientY,

362

continuePropagation: () => {}

363

})}

364

onMouseUp={(e) => handlePressEnd({

365

type: "pressend",

366

pointerType: "mouse",

367

target: e.target as Element,

368

shiftKey: e.shiftKey,

369

ctrlKey: e.ctrlKey,

370

metaKey: e.metaKey,

371

altKey: e.altKey,

372

x: e.clientX,

373

y: e.clientY,

374

continuePropagation: () => {}

375

})}

376

onClick={(e) => handlePress({

377

type: "press",

378

pointerType: "mouse",

379

target: e.target as Element,

380

shiftKey: e.shiftKey,

381

ctrlKey: e.ctrlKey,

382

metaKey: e.metaKey,

383

altKey: e.altKey,

384

x: e.clientX,

385

y: e.clientY,

386

continuePropagation: () => {}

387

})}

388

onMouseEnter={(e) => handleHoverStart({

389

type: "hoverstart",

390

pointerType: "mouse",

391

target: e.target as HTMLElement

392

})}

393

onMouseLeave={(e) => handleHoverEnd({

394

type: "hoverend",

395

pointerType: "mouse",

396

target: e.target as HTMLElement

397

})}

398

onFocus={(e) => {

399

setIsFocused(true);

400

onFocus?.(e);

401

}}

402

onBlur={(e) => {

403

setIsFocused(false);

404

onBlur?.(e);

405

}}

406

onKeyDown={handleKeyDown}

407

style={{

408

backgroundColor: isPressed ? "#ccc" : isHovered ? "#eee" : "#fff",

409

outline: isFocused ? "2px solid blue" : "none",

410

opacity: isDisabled ? 0.5 : 1

411

}}

412

>

413

{children}

414

</button>

415

);

416

}

417

418

// Draggable element with move events

419

interface DraggableProps extends MoveEvents {

420

children: React.ReactNode;

421

}

422

423

function Draggable({ children, onMoveStart, onMove, onMoveEnd }: DraggableProps) {

424

const [position, setPosition] = useState({ x: 0, y: 0 });

425

const [isDragging, setIsDragging] = useState(false);

426

427

const handleMoveStart = (e: MouseEvent) => {

428

setIsDragging(true);

429

onMoveStart?.({

430

type: "movestart",

431

pointerType: "mouse",

432

shiftKey: e.shiftKey,

433

ctrlKey: e.ctrlKey,

434

metaKey: e.metaKey,

435

altKey: e.altKey

436

});

437

};

438

439

const handleMove = (e: MouseEvent) => {

440

if (!isDragging) return;

441

442

const deltaX = e.movementX;

443

const deltaY = e.movementY;

444

445

setPosition(prev => ({

446

x: prev.x + deltaX,

447

y: prev.y + deltaY

448

}));

449

450

onMove?.({

451

type: "move",

452

pointerType: "mouse",

453

deltaX,

454

deltaY,

455

shiftKey: e.shiftKey,

456

ctrlKey: e.ctrlKey,

457

metaKey: e.metaKey,

458

altKey: e.altKey

459

});

460

};

461

462

const handleMoveEnd = (e: MouseEvent) => {

463

setIsDragging(false);

464

onMoveEnd?.({

465

type: "moveend",

466

pointerType: "mouse",

467

shiftKey: e.shiftKey,

468

ctrlKey: e.ctrlKey,

469

metaKey: e.metaKey,

470

altKey: e.altKey

471

});

472

};

473

474

useEffect(() => {

475

if (isDragging) {

476

document.addEventListener("mousemove", handleMove);

477

document.addEventListener("mouseup", handleMoveEnd);

478

return () => {

479

document.removeEventListener("mousemove", handleMove);

480

document.removeEventListener("mouseup", handleMoveEnd);

481

};

482

}

483

}, [isDragging]);

484

485

return (

486

<div

487

style={{

488

position: "absolute",

489

left: position.x,

490

top: position.y,

491

cursor: isDragging ? "grabbing" : "grab"

492

}}

493

onMouseDown={handleMoveStart}

494

>

495

{children}

496

</div>

497

);

498

}

499

500

// Keyboard navigation component

501

interface KeyboardNavigationProps extends KeyboardEvents, FocusableProps {

502

items: string[];

503

onSelect?: (item: string, index: number) => void;

504

}

505

506

function KeyboardNavigation({

507

items,

508

onSelect,

509

onKeyDown,

510

onFocus,

511

onBlur,

512

autoFocus

513

}: KeyboardNavigationProps) {

514

const [selectedIndex, setSelectedIndex] = useState(0);

515

516

const handleKeyDown = (e: KeyboardEvent) => {

517

switch (e.key) {

518

case "ArrowDown":

519

e.preventDefault();

520

setSelectedIndex(prev => Math.min(prev + 1, items.length - 1));

521

break;

522

case "ArrowUp":

523

e.preventDefault();

524

setSelectedIndex(prev => Math.max(prev - 1, 0));

525

break;

526

case "Enter":

527

e.preventDefault();

528

onSelect?.(items[selectedIndex], selectedIndex);

529

break;

530

case "Home":

531

e.preventDefault();

532

setSelectedIndex(0);

533

break;

534

case "End":

535

e.preventDefault();

536

setSelectedIndex(items.length - 1);

537

break;

538

}

539

onKeyDown?.(e);

540

};

541

542

return (

543

<div

544

tabIndex={0}

545

autoFocus={autoFocus}

546

onKeyDown={handleKeyDown}

547

onFocus={onFocus}

548

onBlur={onBlur}

549

role="listbox"

550

aria-activedescendant={`item-${selectedIndex}`}

551

>

552

{items.map((item, index) => (

553

<div

554

key={index}

555

id={`item-${index}`}

556

role="option"

557

aria-selected={index === selectedIndex}

558

style={{

559

backgroundColor: index === selectedIndex ? "#e0e0e0" : "transparent",

560

padding: "8px"

561

}}

562

onClick={() => {

563

setSelectedIndex(index);

564

onSelect?.(item, index);

565

}}

566

>

567

{item}

568

</div>

569

))}

570

</div>

571

);

572

}

573

```