or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

composables.mdcore-ui.mdgraphics.mdindex.mdinput.mdios-integration.mdlayout.mdmaterial-design.mdresources.mdstate.mdtext.mdwindow.md

input.mddocs/

0

# Input and Interaction

1

2

Multi-platform input handling system supporting touch, mouse, keyboard input, and gesture recognition with comprehensive focus management and accessibility features.

3

4

## Capabilities

5

6

### Pointer Input System

7

8

Low-level pointer input handling supporting touch, mouse, and stylus input with gesture recognition capabilities.

9

10

```kotlin { .api }

11

/**

12

* Modifier that provides access to pointer input events.

13

*/

14

fun Modifier.pointerInput(

15

key1: Any?,

16

block: suspend PointerInputScope.() -> Unit

17

): Modifier

18

19

/**

20

* Modifier that provides access to pointer input events with multiple keys.

21

*/

22

fun Modifier.pointerInput(

23

key1: Any?,

24

key2: Any?,

25

block: suspend PointerInputScope.() -> Unit

26

): Modifier

27

28

/**

29

* Modifier that provides access to pointer input events with arbitrary keys.

30

*/

31

fun Modifier.pointerInput(

32

vararg keys: Any?,

33

block: suspend PointerInputScope.() -> Unit

34

): Modifier

35

36

/**

37

* Scope for handling pointer input events.

38

*/

39

interface PointerInputScope : Density {

40

/**

41

* The size of the pointer input region.

42

*/

43

val size: IntSize

44

45

/**

46

* The extended touch slop for gestures.

47

*/

48

val extendedTouchSlop: Float

49

50

/**

51

* Configuration for pointer input behavior.

52

*/

53

val viewConfiguration: ViewConfiguration

54

55

/**

56

* Wait for a pointer event to occur.

57

*/

58

suspend fun awaitPointerEventScope(

59

pass: PointerEventPass = PointerEventPass.Main,

60

block: suspend AwaitPointerEventScope.() -> Unit

61

)

62

}

63

64

/**

65

* Scope for awaiting and handling pointer events.

66

*/

67

interface AwaitPointerEventScope : Density {

68

/**

69

* The size of the pointer input region.

70

*/

71

val size: IntSize

72

73

/**

74

* The current pointer event.

75

*/

76

val currentEvent: PointerEvent

77

78

/**

79

* Extended touch slop configuration.

80

*/

81

val extendedTouchSlop: Float

82

83

/**

84

* View configuration for pointer input.

85

*/

86

val viewConfiguration: ViewConfiguration

87

88

/**

89

* Wait for the next pointer event.

90

*/

91

suspend fun awaitPointerEvent(

92

pass: PointerEventPass = PointerEventPass.Main

93

): PointerEvent

94

95

/**

96

* Attempt to drag one pointer.

97

*/

98

suspend fun drag(

99

pointerId: PointerId,

100

onDrag: (PointerInputChange) -> Unit

101

): Boolean

102

103

/**

104

* Wait for all pointers to be up.

105

*/

106

suspend fun waitForUpOrCancellation(

107

pass: PointerEventPass = PointerEventPass.Main

108

): PointerInputChange?

109

}

110

111

/**

112

* Represents a pointer event containing information about pointer changes.

113

*/

114

class PointerEvent(

115

val changes: List<PointerInputChange>,

116

val buttons: PointerButtons = PointerButtons(),

117

val keyboardModifiers: PointerKeyboardModifiers = PointerKeyboardModifiers(),

118

val type: PointerEventType = PointerEventType.Unknown

119

) {

120

/**

121

* The number of pointers in this event.

122

*/

123

val pointerCount: Int

124

125

/**

126

* Get a pointer change by its ID.

127

*/

128

fun getPointerById(pointerId: PointerId): PointerInputChange?

129

}

130

131

/**

132

* Information about a single pointer's state changes.

133

*/

134

@Immutable

135

data class PointerInputChange(

136

val id: PointerId,

137

val uptimeMillis: Long,

138

val position: Offset,

139

val pressed: Boolean,

140

val pressure: Float = 1.0f,

141

val previousUptimeMillis: Long = uptimeMillis,

142

val previousPosition: Offset = position,

143

val previousPressed: Boolean = pressed,

144

val isConsumed: Boolean = false,

145

val type: PointerType = PointerType.Unknown,

146

val scrollDelta: Offset = Offset.Zero

147

) {

148

/**

149

* Whether this pointer change represents a press event.

150

*/

151

val changedToDown: Boolean

152

153

/**

154

* Whether this pointer change represents a release event.

155

*/

156

val changedToUp: Boolean

157

158

/**

159

* Whether this pointer change represents movement.

160

*/

161

val changedToDownIgnoreConsumed: Boolean

162

163

/**

164

* The change in position.

165

*/

166

val positionChange: Offset

167

168

/**

169

* The change in position, ignoring consumption.

170

*/

171

val positionChangeIgnoreConsumed: Offset

172

173

/**

174

* Consume this pointer change.

175

*/

176

fun consume()

177

178

/**

179

* Copy this change with consumption applied.

180

*/

181

fun consumeAllChanges(): PointerInputChange

182

183

/**

184

* Copy this change with position change consumed.

185

*/

186

fun consumePositionChange(): PointerInputChange

187

}

188

189

/**

190

* Unique identifier for a pointer.

191

*/

192

@JvmInline

193

value class PointerId(val value: Long)

194

195

/**

196

* Types of pointer events.

197

*/

198

enum class PointerEventType {

199

Unknown, Press, Release, Move, Enter, Exit, Scroll

200

}

201

202

/**

203

* Types of pointers.

204

*/

205

enum class PointerType {

206

Unknown, Touch, Mouse, Stylus, Eraser

207

}

208

209

/**

210

* Pointer event pass for event handling phases.

211

*/

212

enum class PointerEventPass {

213

Initial, Main, Final

214

}

215

```

216

217

**Usage Examples:**

218

219

```kotlin

220

// Basic pointer input handling

221

@Composable

222

fun PointerInputExample() {

223

var position by remember { mutableStateOf(Offset.Zero) }

224

var isPressed by remember { mutableStateOf(false) }

225

226

Box(

227

modifier = Modifier

228

.size(200.dp)

229

.background(if (isPressed) Color.Red else Color.Blue)

230

.pointerInput(Unit) {

231

awaitPointerEventScope {

232

while (true) {

233

val event = awaitPointerEvent()

234

val change = event.changes.first()

235

236

position = change.position

237

isPressed = change.pressed

238

239

if (change.changedToDown || change.changedToUp) {

240

change.consume()

241

}

242

}

243

}

244

}

245

) {

246

Text(

247

text = "Position: (${position.x.toInt()}, ${position.y.toInt()})\nPressed: $isPressed",

248

color = Color.White,

249

modifier = Modifier.align(Alignment.Center)

250

)

251

}

252

}

253

254

// Multi-touch handling

255

@Composable

256

fun MultiTouchExample() {

257

var pointers by remember { mutableStateOf(mapOf<PointerId, Offset>()) }

258

259

Box(

260

modifier = Modifier

261

.fillMaxSize()

262

.pointerInput(Unit) {

263

awaitPointerEventScope {

264

while (true) {

265

val event = awaitPointerEvent()

266

267

pointers = event.changes.associate { change ->

268

if (change.pressed) {

269

change.id to change.position

270

} else {

271

change.id to Offset.Unspecified

272

}

273

}.filterValues { it != Offset.Unspecified }

274

275

event.changes.forEach { it.consume() }

276

}

277

}

278

}

279

) {

280

pointers.forEach { (id, position) ->

281

Box(

282

modifier = Modifier

283

.offset(position.x.dp - 25.dp, position.y.dp - 25.dp)

284

.size(50.dp)

285

.background(Color.Red, CircleShape)

286

)

287

}

288

}

289

}

290

```

291

292

### High-Level Gesture Detection

293

294

Pre-built gesture detectors for common interaction patterns like taps, drags, and swipes.

295

296

```kotlin { .api }

297

/**

298

* Detects tap gestures.

299

*/

300

suspend fun PointerInputScope.detectTapGestures(

301

onDoubleTap: ((Offset) -> Unit)? = null,

302

onLongPress: ((Offset) -> Unit)? = null,

303

onPress: (suspend PressGestureScope.(Offset) -> Unit)? = null,

304

onTap: ((Offset) -> Unit)? = null

305

)

306

307

/**

308

* Detects drag gestures.

309

*/

310

suspend fun PointerInputScope.detectDragGestures(

311

onDragStart: (Offset) -> Unit = { },

312

onDragEnd: () -> Unit = { },

313

onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit

314

)

315

316

/**

317

* Detects drag gestures after a long press.

318

*/

319

suspend fun PointerInputScope.detectDragGesturesAfterLongPress(

320

onDragStart: (Offset) -> Unit = { },

321

onDragEnd: () -> Unit = { },

322

onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit

323

)

324

325

/**

326

* Detects horizontal drag gestures.

327

*/

328

suspend fun PointerInputScope.detectHorizontalDragGestures(

329

onDragStart: (Offset) -> Unit = { },

330

onDragEnd: () -> Unit = { },

331

onHorizontalDrag: (change: PointerInputChange, dragAmount: Float) -> Unit

332

)

333

334

/**

335

* Detects vertical drag gestures.

336

*/

337

suspend fun PointerInputScope.detectVerticalDragGestures(

338

onDragStart: (Offset) -> Unit = { },

339

onDragEnd: () -> Unit = { },

340

onVerticalDrag: (change: PointerInputChange, dragAmount: Float) -> Unit

341

)

342

343

/**

344

* Detects rotation and zoom gestures.

345

*/

346

suspend fun PointerInputScope.detectTransformGestures(

347

panZoomLock: Boolean = false,

348

onGesture: (centroid: Offset, pan: Offset, zoom: Float, rotation: Float) -> Unit

349

)

350

351

/**

352

* Scope for press gestures.

353

*/

354

interface PressGestureScope : Density {

355

/**

356

* Try to await release of the press.

357

*/

358

suspend fun tryAwaitRelease(): Boolean

359

360

/**

361

* Await release of the press.

362

*/

363

suspend fun awaitRelease()

364

}

365

366

/**

367

* Configuration for drag gestures.

368

*/

369

data class DragGestureDetectorConfig(

370

val canDrag: (PointerInputChange) -> Boolean = { true }

371

)

372

```

373

374

**Usage Examples:**

375

376

```kotlin

377

// Tap gesture detection

378

@Composable

379

fun TapGestureExample() {

380

var tapCount by remember { mutableStateOf(0) }

381

var lastTapPosition by remember { mutableStateOf(Offset.Zero) }

382

383

Box(

384

modifier = Modifier

385

.size(200.dp)

386

.background(Color.Green)

387

.pointerInput(Unit) {

388

detectTapGestures(

389

onTap = { offset ->

390

tapCount++

391

lastTapPosition = offset

392

},

393

onDoubleTap = { offset ->

394

tapCount += 2

395

lastTapPosition = offset

396

},

397

onLongPress = { offset ->

398

tapCount = 0

399

lastTapPosition = offset

400

}

401

)

402

}

403

) {

404

Text(

405

text = "Taps: $tapCount\nLast: (${lastTapPosition.x.toInt()}, ${lastTapPosition.y.toInt()})",

406

color = Color.White,

407

modifier = Modifier.align(Alignment.Center)

408

)

409

}

410

}

411

412

// Drag gesture detection

413

@Composable

414

fun DragGestureExample() {

415

var offset by remember { mutableStateOf(Offset.Zero) }

416

var isDragging by remember { mutableStateOf(false) }

417

418

Box(

419

modifier = Modifier.fillMaxSize()

420

) {

421

Box(

422

modifier = Modifier

423

.offset(offset.x.dp, offset.y.dp)

424

.size(100.dp)

425

.background(if (isDragging) Color.Red else Color.Blue)

426

.pointerInput(Unit) {

427

detectDragGestures(

428

onDragStart = {

429

isDragging = true

430

},

431

onDragEnd = {

432

isDragging = false

433

},

434

onDrag = { change, dragAmount ->

435

offset += dragAmount

436

}

437

)

438

}

439

)

440

}

441

}

442

443

// Transform gestures (zoom, rotate, pan)

444

@Composable

445

fun TransformGestureExample() {

446

var scale by remember { mutableStateOf(1f) }

447

var rotation by remember { mutableStateOf(0f) }

448

var offset by remember { mutableStateOf(Offset.Zero) }

449

450

Box(

451

modifier = Modifier

452

.fillMaxSize()

453

.pointerInput(Unit) {

454

detectTransformGestures { centroid, pan, zoom, rotationChange ->

455

scale *= zoom

456

rotation += rotationChange

457

offset += pan

458

}

459

}

460

) {

461

Box(

462

modifier = Modifier

463

.size(200.dp)

464

.offset(offset.x.dp, offset.y.dp)

465

.scale(scale)

466

.rotate(rotation)

467

.background(Color.Yellow)

468

.align(Alignment.Center)

469

) {

470

Text(

471

text = "Transform me!",

472

modifier = Modifier.align(Alignment.Center)

473

)

474

}

475

}

476

}

477

```

478

479

### Clickable and Interactive Modifiers

480

481

High-level modifiers for common interactive behaviors like clicks, selections, and toggles.

482

483

```kotlin { .api }

484

/**

485

* Modifier that makes a component clickable.

486

*/

487

fun Modifier.clickable(

488

enabled: Boolean = true,

489

onClickLabel: String? = null,

490

role: Role? = null,

491

onClick: () -> Unit

492

): Modifier

493

494

/**

495

* Modifier that makes a component clickable with custom indication.

496

*/

497

fun Modifier.clickable(

498

interactionSource: MutableInteractionSource,

499

indication: Indication?,

500

enabled: Boolean = true,

501

onClickLabel: String? = null,

502

role: Role? = null,

503

onClick: () -> Unit

504

): Modifier

505

506

/**

507

* Modifier that makes a component selectable.

508

*/

509

fun Modifier.selectable(

510

selected: Boolean,

511

enabled: Boolean = true,

512

role: Role? = null,

513

onValueChange: (Boolean) -> Unit

514

): Modifier

515

516

/**

517

* Modifier that makes a component toggleable.

518

*/

519

fun Modifier.toggleable(

520

value: Boolean,

521

enabled: Boolean = true,

522

role: Role? = null,

523

onValueChange: (Boolean) -> Unit

524

): Modifier

525

526

/**

527

* Modifier that makes a component tristate toggleable.

528

*/

529

fun Modifier.triStateToggleable(

530

state: ToggleableState,

531

enabled: Boolean = true,

532

role: Role? = null,

533

onClick: () -> Unit

534

): Modifier

535

536

/**

537

* Represents the state of a toggleable component.

538

*/

539

enum class ToggleableState {

540

On, Off, Indeterminate

541

}

542

543

/**

544

* Semantic roles for accessibility.

545

*/

546

enum class Role {

547

Button, Checkbox, Switch, RadioButton, Tab, Image, DropdownList

548

}

549

```

550

551

**Usage Examples:**

552

553

```kotlin

554

// Basic clickable component

555

@Composable

556

fun ClickableExample() {

557

var clickCount by remember { mutableStateOf(0) }

558

559

Box(

560

modifier = Modifier

561

.size(150.dp)

562

.background(Color.Blue)

563

.clickable {

564

clickCount++

565

}

566

) {

567

Text(

568

text = "Clicked: $clickCount",

569

color = Color.White,

570

modifier = Modifier.align(Alignment.Center)

571

)

572

}

573

}

574

575

// Selectable component

576

@Composable

577

fun SelectableExample() {

578

var selected by remember { mutableStateOf(false) }

579

580

Row(

581

modifier = Modifier

582

.fillMaxWidth()

583

.selectable(

584

selected = selected,

585

onClick = { selected = !selected },

586

role = Role.Checkbox

587

)

588

.padding(16.dp)

589

) {

590

Box(

591

modifier = Modifier

592

.size(24.dp)

593

.background(

594

if (selected) Color.Blue else Color.Gray,

595

RoundedCornerShape(4.dp)

596

)

597

) {

598

if (selected) {

599

Text(

600

text = "✓",

601

color = Color.White,

602

modifier = Modifier.align(Alignment.Center)

603

)

604

}

605

}

606

607

Spacer(modifier = Modifier.width(16.dp))

608

609

Text(text = "Select this option")

610

}

611

}

612

613

// Toggleable component

614

@Composable

615

fun ToggleableExample() {

616

var isOn by remember { mutableStateOf(false) }

617

618

Row(

619

modifier = Modifier

620

.toggleable(

621

value = isOn,

622

onValueChange = { isOn = it },

623

role = Role.Switch

624

)

625

.padding(16.dp),

626

verticalAlignment = Alignment.CenterVertically

627

) {

628

Text(text = "Enable feature")

629

630

Spacer(modifier = Modifier.width(16.dp))

631

632

Box(

633

modifier = Modifier

634

.size(50.dp, 30.dp)

635

.background(

636

if (isOn) Color.Green else Color.Gray,

637

RoundedCornerShape(15.dp)

638

)

639

) {

640

Box(

641

modifier = Modifier

642

.size(26.dp)

643

.background(Color.White, CircleShape)

644

.align(if (isOn) Alignment.CenterEnd else Alignment.CenterStart)

645

)

646

}

647

}

648

}

649

650

// Custom interaction source and indication

651

@Composable

652

fun CustomInteractionExample() {

653

val interactionSource = remember { MutableInteractionSource() }

654

val isPressed by interactionSource.collectIsPressedAsState()

655

val isHovered by interactionSource.collectIsHoveredAsState()

656

657

Box(

658

modifier = Modifier

659

.size(150.dp)

660

.background(

661

when {

662

isPressed -> Color.Red

663

isHovered -> Color.Yellow

664

else -> Color.Blue

665

}

666

)

667

.clickable(

668

interactionSource = interactionSource,

669

indication = null // Custom indication handling

670

) {

671

// Handle click

672

}

673

) {

674

Text(

675

text = when {

676

isPressed -> "Pressed"

677

isHovered -> "Hovered"

678

else -> "Normal"

679

},

680

color = Color.White,

681

modifier = Modifier.align(Alignment.Center)

682

)

683

}

684

}

685

```

686

687

### Focus Management

688

689

Comprehensive focus handling system for keyboard navigation and accessibility.

690

691

```kotlin { .api }

692

/**

693

* Modifier that makes a component focusable.

694

*/

695

fun Modifier.focusable(

696

enabled: Boolean = true,

697

interactionSource: MutableInteractionSource? = null

698

): Modifier

699

700

/**

701

* Modifier that specifies focus behavior.

702

*/

703

fun Modifier.focusTarget(): Modifier

704

705

/**

706

* Modifier that specifies focus properties.

707

*/

708

fun Modifier.focusProperties(scope: FocusProperties.() -> Unit): Modifier

709

710

/**

711

* Modifier for observing focus changes.

712

*/

713

fun Modifier.onFocusChanged(onFocusChanged: (FocusState) -> Unit): Modifier

714

715

/**

716

* Modifier for observing focus events.

717

*/

718

fun Modifier.onFocusEvent(onFocusEvent: (FocusState) -> Unit): Modifier

719

720

/**

721

* Represents the focus state of a component.

722

*/

723

interface FocusState {

724

/**

725

* Whether this component has focus.

726

*/

727

val hasFocus: Boolean

728

729

/**

730

* Whether this component is focused or contains a focused descendant.

731

*/

732

val isFocused: Boolean

733

734

/**

735

* Whether focus is captured by this component.

736

*/

737

val isCaptured: Boolean

738

}

739

740

/**

741

* Properties for configuring focus behavior.

742

*/

743

interface FocusProperties {

744

/**

745

* Whether this component can be focused.

746

*/

747

var canFocus: Boolean

748

749

/**

750

* Custom focus enter behavior.

751

*/

752

var enter: (FocusDirection) -> FocusRequester?

753

754

/**

755

* Custom focus exit behavior.

756

*/

757

var exit: (FocusDirection) -> FocusRequester?

758

759

/**

760

* Focus order within the parent.

761

*/

762

var next: FocusRequester?

763

var previous: FocusRequester?

764

var up: FocusRequester?

765

var down: FocusRequester?

766

var left: FocusRequester?

767

var right: FocusRequester?

768

var start: FocusRequester?

769

var end: FocusRequester?

770

}

771

772

/**

773

* Used to request focus programmatically.

774

*/

775

class FocusRequester {

776

/**

777

* Request focus for this component.

778

*/

779

fun requestFocus()

780

781

/**

782

* Capture focus for this component.

783

*/

784

fun captureFocus(): Boolean

785

786

/**

787

* Free captured focus.

788

*/

789

fun freeFocus(): Boolean

790

791

companion object {

792

/**

793

* Default focus requester.

794

*/

795

val Default: FocusRequester

796

797

/**

798

* Focus requester that cancels focus.

799

*/

800

val Cancel: FocusRequester

801

}

802

}

803

804

/**

805

* Directions for focus movement.

806

*/

807

enum class FocusDirection {

808

Next, Previous, Up, Down, Left, Right, In, Out, Enter, Exit

809

}

810

811

/**

812

* CompositionLocal for accessing the focus manager.

813

*/

814

val LocalFocusManager: ProvidableCompositionLocal<FocusManager>

815

816

/**

817

* Interface for managing focus.

818

*/

819

interface FocusManager {

820

/**

821

* Clear focus from the currently focused component.

822

*/

823

fun clearFocus(force: Boolean = false)

824

825

/**

826

* Move focus in the specified direction.

827

*/

828

fun moveFocus(focusDirection: FocusDirection): Boolean

829

}

830

```

831

832

**Usage Examples:**

833

834

```kotlin

835

// Basic focus handling

836

@Composable

837

fun FocusExample() {

838

val focusRequester = remember { FocusRequester() }

839

var isFocused by remember { mutableStateOf(false) }

840

841

Column {

842

Box(

843

modifier = Modifier

844

.size(100.dp)

845

.background(if (isFocused) Color.Blue else Color.Gray)

846

.focusRequester(focusRequester)

847

.focusable()

848

.onFocusChanged { focusState ->

849

isFocused = focusState.isFocused

850

}

851

) {

852

Text(

853

text = if (isFocused) "Focused" else "Not Focused",

854

color = Color.White,

855

modifier = Modifier.align(Alignment.Center)

856

)

857

}

858

859

Button(

860

onClick = { focusRequester.requestFocus() }

861

) {

862

Text("Request Focus")

863

}

864

}

865

}

866

867

// Custom focus navigation

868

@Composable

869

fun FocusNavigationExample() {

870

val focusRequesters = remember { List(4) { FocusRequester() } }

871

val focusManager = LocalFocusManager.current

872

873

LazyVerticalGrid(

874

columns = GridCells.Fixed(2),

875

modifier = Modifier.padding(16.dp)

876

) {

877

items(4) { index ->

878

var isFocused by remember { mutableStateOf(false) }

879

880

Box(

881

modifier = Modifier

882

.size(100.dp)

883

.padding(4.dp)

884

.background(

885

if (isFocused) Color.Blue else Color.LightGray,

886

RoundedCornerShape(8.dp)

887

)

888

.focusRequester(focusRequesters[index])

889

.focusProperties {

890

// Custom focus navigation

891

next = if (index < 3) focusRequesters[index + 1] else FocusRequester.Default

892

previous = if (index > 0) focusRequesters[index - 1] else FocusRequester.Default

893

894

// Grid-like navigation

895

down = if (index < 2) focusRequesters[index + 2] else FocusRequester.Default

896

up = if (index >= 2) focusRequesters[index - 2] else FocusRequester.Default

897

}

898

.focusable()

899

.onFocusChanged { focusState ->

900

isFocused = focusState.isFocused

901

}

902

.clickable {

903

focusRequesters[index].requestFocus()

904

}

905

) {

906

Text(

907

text = "Item ${index + 1}",

908

color = if (isFocused) Color.White else Color.Black,

909

modifier = Modifier.align(Alignment.Center)

910

)

911

}

912

}

913

}

914

}

915

916

// Focus capture for modal behavior

917

@Composable

918

fun FocusCaptureExample() {

919

var showModal by remember { mutableStateOf(false) }

920

val modalFocusRequester = remember { FocusRequester() }

921

922

Box(modifier = Modifier.fillMaxSize()) {

923

Column {

924

Button(onClick = { showModal = true }) {

925

Text("Show Modal")

926

}

927

928

Text("This content becomes unfocusable when modal is shown")

929

930

Button(onClick = { /* Regular button */ }) {

931

Text("Regular Button")

932

}

933

}

934

935

if (showModal) {

936

Box(

937

modifier = Modifier

938

.fillMaxSize()

939

.background(Color.Black.copy(alpha = 0.5f))

940

.clickable { showModal = false }

941

) {

942

Box(

943

modifier = Modifier

944

.size(300.dp, 200.dp)

945

.background(Color.White, RoundedCornerShape(8.dp))

946

.align(Alignment.Center)

947

.focusRequester(modalFocusRequester)

948

.focusProperties {

949

// Capture focus to prevent navigation outside modal

950

canFocus = true

951

}

952

.focusable()

953

) {

954

Column(

955

modifier = Modifier

956

.fillMaxSize()

957

.padding(16.dp),

958

verticalArrangement = Arrangement.SpaceAround,

959

horizontalAlignment = Alignment.CenterHorizontally

960

) {

961

Text("Modal Dialog")

962

Button(onClick = { showModal = false }) {

963

Text("Close")

964

}

965

}

966

}

967

}

968

969

LaunchedEffect(showModal) {

970

modalFocusRequester.requestFocus()

971

}

972

}

973

}

974

}

975

```

976

977

### Keyboard Input

978

979

Keyboard event handling for text input, shortcuts, and navigation keys.

980

981

```kotlin { .api }

982

/**

983

* Modifier for handling key events.

984

*/

985

fun Modifier.onKeyEvent(

986

onKeyEvent: (KeyEvent) -> Boolean

987

): Modifier

988

989

/**

990

* Modifier for handling pre-view key events.

991

*/

992

fun Modifier.onPreviewKeyEvent(

993

onPreviewKeyEvent: (KeyEvent) -> Boolean

994

): Modifier

995

996

/**

997

* Represents a keyboard event.

998

*/

999

expect class KeyEvent {

1000

/**

1001

* The key that was pressed or released.

1002

*/

1003

val key: Key

1004

1005

/**

1006

* The type of key event.

1007

*/

1008

val type: KeyEventType

1009

1010

/**

1011

* Whether the Alt key is pressed.

1012

*/

1013

val isAltPressed: Boolean

1014

1015

/**

1016

* Whether the Ctrl key is pressed.

1017

*/

1018

val isCtrlPressed: Boolean

1019

1020

/**

1021

* Whether the Meta key is pressed.

1022

*/

1023

val isMetaPressed: Boolean

1024

1025

/**

1026

* Whether the Shift key is pressed.

1027

*/

1028

val isShiftPressed: Boolean

1029

}

1030

1031

/**

1032

* Types of key events.

1033

*/

1034

enum class KeyEventType {

1035

KeyDown, KeyUp, Unknown

1036

}

1037

1038

/**

1039

* Represents keyboard keys.

1040

*/

1041

expect class Key {

1042

companion object {

1043

val A: Key

1044

val B: Key

1045

// ... other alphabet keys

1046

val Zero: Key

1047

val One: Key

1048

// ... other number keys

1049

val Enter: Key

1050

val Escape: Key

1051

val Backspace: Key

1052

val Delete: Key

1053

val Tab: Key

1054

val Spacebar: Key

1055

val DirectionUp: Key

1056

val DirectionDown: Key

1057

val DirectionLeft: Key

1058

val DirectionRight: Key

1059

val PageUp: Key

1060

val PageDown: Key

1061

val Home: Key

1062

val MoveEnd: Key

1063

val F1: Key

1064

val F2: Key

1065

// ... other function keys

1066

}

1067

}

1068

```

1069

1070

**Usage Examples:**

1071

1072

```kotlin

1073

// Keyboard event handling

1074

@Composable

1075

fun KeyboardHandlingExample() {

1076

var lastKey by remember { mutableStateOf("None") }

1077

var keyCount by remember { mutableStateOf(0) }

1078

1079

Box(

1080

modifier = Modifier

1081

.size(300.dp, 200.dp)

1082

.background(Color.LightGray, RoundedCornerShape(8.dp))

1083

.focusable()

1084

.onKeyEvent { keyEvent ->

1085

if (keyEvent.type == KeyEventType.KeyDown) {

1086

lastKey = keyEvent.key.toString()

1087

keyCount++

1088

1089

// Handle specific keys

1090

when (keyEvent.key) {

1091

Key.Escape -> {

1092

lastKey = "Escape pressed"

1093

true // Consume event

1094

}

1095

Key.Enter -> {

1096

lastKey = "Enter pressed"

1097

true

1098

}

1099

else -> false // Don't consume

1100

}

1101

} else {

1102

false

1103

}

1104

}

1105

) {

1106

Column(

1107

modifier = Modifier.align(Alignment.Center),

1108

horizontalAlignment = Alignment.CenterHorizontally

1109

) {

1110

Text("Focus this box and press keys")

1111

Text("Last key: $lastKey")

1112

Text("Key count: $keyCount")

1113

}

1114

}

1115

}

1116

1117

// Keyboard shortcuts

1118

@Composable

1119

fun KeyboardShortcutsExample() {

1120

var content by remember { mutableStateOf("Type here...") }

1121

var saved by remember { mutableStateOf(false) }

1122

1123

Column(

1124

modifier = Modifier

1125

.fillMaxSize()

1126

.padding(16.dp)

1127

.onPreviewKeyEvent { keyEvent ->

1128

if (keyEvent.type == KeyEventType.KeyDown && keyEvent.isCtrlPressed) {

1129

when (keyEvent.key) {

1130

Key.S -> {

1131

// Ctrl+S to save

1132

saved = true

1133

true

1134

}

1135

Key.N -> {

1136

// Ctrl+N for new

1137

content = ""

1138

saved = false

1139

true

1140

}

1141

else -> false

1142

}

1143

} else {

1144

false

1145

}

1146

}

1147

) {

1148

if (saved) {

1149

Text(

1150

text = "Saved!",

1151

color = Color.Green,

1152

fontWeight = FontWeight.Bold

1153

)

1154

} else {

1155

Text(

1156

text = "Unsaved changes",

1157

color = Color.Orange

1158

)

1159

}

1160

1161

Spacer(modifier = Modifier.height(16.dp))

1162

1163

TextField(

1164

value = content,

1165

onValueChange = {

1166

content = it

1167

saved = false

1168

},

1169

label = { Text("Content (Ctrl+S to save, Ctrl+N for new)") },

1170

modifier = Modifier.fillMaxWidth()

1171

)

1172

1173

Spacer(modifier = Modifier.height(16.dp))

1174

1175

Text("Keyboard shortcuts:")

1176

Text("• Ctrl+S: Save")

1177

Text("• Ctrl+N: New document")

1178

}

1179

}

1180

```