or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

browser-apis.mdbrowser-events.mddevice-sensors.mdelement-tracking.mdindex.mdmouse-pointer.mdscroll-resize.mdtheme-preferences.mdutilities-advanced.mdwindow-document.md

device-sensors.mddocs/

0

# Device and Sensors

1

2

Components for accessing device information, sensors, and hardware capabilities.

3

4

## Capabilities

5

6

### UseBattery Component

7

8

Provides battery status information from the Battery Status API.

9

10

```typescript { .api }

11

/**

12

* Component that provides battery status information

13

* @example

14

* <UseBattery v-slot="{ charging, level, dischargingTime }">

15

* <div>Battery: {{ Math.round(level * 100) }}% {{ charging ? 'charging' : 'discharging' }}</div>

16

* </UseBattery>

17

*/

18

interface UseBatteryProps {

19

// No props - uses browser Battery API directly

20

}

21

22

/** Slot data exposed by UseBattery component */

23

interface BatteryState {

24

/** Whether the battery is charging */

25

charging: Ref<boolean>;

26

/** Time remaining until charged (seconds) */

27

chargingTime: Ref<number>;

28

/** Time remaining until discharged (seconds) */

29

dischargingTime: Ref<number>;

30

/** Battery charge level (0-1) */

31

level: Ref<number>;

32

/** Whether the Battery API is supported */

33

isSupported: Ref<boolean>;

34

}

35

```

36

37

**Usage Examples:**

38

39

```vue

40

<template>

41

<!-- Basic battery info -->

42

<UseBattery v-slot="{ charging, level, isSupported }">

43

<div v-if="isSupported" class="battery-info">

44

<div class="battery-level" :style="{ width: level * 100 + '%' }"></div>

45

<span>{{ Math.round(level * 100) }}% {{ charging ? '🔌' : '🔋' }}</span>

46

</div>

47

<div v-else>Battery API not supported</div>

48

</UseBattery>

49

50

<!-- Detailed battery info -->

51

<UseBattery v-slot="{ charging, level, chargingTime, dischargingTime }">

52

<div class="detailed-battery">

53

<h3>Battery Status</h3>

54

<p>Level: {{ Math.round(level * 100) }}%</p>

55

<p>Status: {{ charging ? 'Charging' : 'Discharging' }}</p>

56

<p v-if="charging && chargingTime !== Infinity">

57

Time to full: {{ Math.round(chargingTime / 60) }} minutes

58

</p>

59

<p v-if="!charging && dischargingTime !== Infinity">

60

Time remaining: {{ Math.round(dischargingTime / 60) }} minutes

61

</p>

62

</div>

63

</UseBattery>

64

</template>

65

66

<script setup>

67

import { UseBattery } from '@vueuse/components';

68

</script>

69

70

<style>

71

.battery-info {

72

position: relative;

73

width: 200px;

74

height: 30px;

75

border: 2px solid #333;

76

border-radius: 4px;

77

overflow: hidden;

78

display: flex;

79

align-items: center;

80

justify-content: center;

81

}

82

83

.battery-level {

84

position: absolute;

85

left: 0;

86

top: 0;

87

height: 100%;

88

background: linear-gradient(90deg, #4caf50, #8bc34a);

89

transition: width 0.3s ease;

90

}

91

</style>

92

```

93

94

### UseDeviceMotion Component

95

96

Provides device motion sensor data including acceleration and rotation.

97

98

```typescript { .api }

99

/**

100

* Component that provides device motion sensor data

101

* @example

102

* <UseDeviceMotion v-slot="{ acceleration, rotationRate }">

103

* <div>Acceleration: {{ acceleration.x.toFixed(2) }}, {{ acceleration.y.toFixed(2) }}</div>

104

* </UseDeviceMotion>

105

*/

106

interface UseDeviceMotionProps {

107

// No props - uses browser DeviceMotion API directly

108

}

109

110

/** Slot data exposed by UseDeviceMotion component */

111

interface DeviceMotionState {

112

/** Device acceleration data */

113

acceleration: Ref<DeviceMotionAcceleration | null>;

114

/** Device acceleration including gravity */

115

accelerationIncludingGravity: Ref<DeviceMotionAcceleration | null>;

116

/** Device rotation rate data */

117

rotationRate: Ref<DeviceMotionRotationRate | null>;

118

/** Motion interval in milliseconds */

119

interval: Ref<number>;

120

/** Whether the DeviceMotion API is supported */

121

isSupported: Ref<boolean>;

122

}

123

124

interface DeviceMotionAcceleration {

125

/** Acceleration along x-axis (m/s²) */

126

x: number | null;

127

/** Acceleration along y-axis (m/s²) */

128

y: number | null;

129

/** Acceleration along z-axis (m/s²) */

130

z: number | null;

131

}

132

133

interface DeviceMotionRotationRate {

134

/** Rotation around x-axis (degrees/second) */

135

alpha: number | null;

136

/** Rotation around y-axis (degrees/second) */

137

beta: number | null;

138

/** Rotation around z-axis (degrees/second) */

139

gamma: number | null;

140

}

141

```

142

143

**Usage Examples:**

144

145

```vue

146

<template>

147

<!-- Basic motion tracking -->

148

<UseDeviceMotion v-slot="{ acceleration, rotationRate, isSupported }">

149

<div v-if="isSupported" class="motion-display">

150

<h3>Device Motion</h3>

151

<div v-if="acceleration">

152

<h4>Acceleration (m/s²):</h4>

153

<p>X: {{ acceleration.x?.toFixed(2) ?? 'N/A' }}</p>

154

<p>Y: {{ acceleration.y?.toFixed(2) ?? 'N/A' }}</p>

155

<p>Z: {{ acceleration.z?.toFixed(2) ?? 'N/A' }}</p>

156

</div>

157

<div v-if="rotationRate">

158

<h4>Rotation Rate (°/s):</h4>

159

<p>Alpha: {{ rotationRate.alpha?.toFixed(2) ?? 'N/A' }}</p>

160

<p>Beta: {{ rotationRate.beta?.toFixed(2) ?? 'N/A' }}</p>

161

<p>Gamma: {{ rotationRate.gamma?.toFixed(2) ?? 'N/A' }}</p>

162

</div>

163

</div>

164

<div v-else>Device motion not supported</div>

165

</UseDeviceMotion>

166

167

<!-- Motion with gravity -->

168

<UseDeviceMotion v-slot="{ accelerationIncludingGravity, interval }">

169

<div class="gravity-motion">

170

<h3>Motion with Gravity</h3>

171

<p>Update interval: {{ interval }}ms</p>

172

<div v-if="accelerationIncludingGravity">

173

<p>X: {{ accelerationIncludingGravity.x?.toFixed(3) }}</p>

174

<p>Y: {{ accelerationIncludingGravity.y?.toFixed(3) }}</p>

175

<p>Z: {{ accelerationIncludingGravity.z?.toFixed(3) }}</p>

176

</div>

177

</div>

178

</UseDeviceMotion>

179

</template>

180

181

<script setup>

182

import { UseDeviceMotion } from '@vueuse/components';

183

</script>

184

```

185

186

### UseDeviceOrientation Component

187

188

Provides device orientation data including compass heading and device tilt.

189

190

```typescript { .api }

191

/**

192

* Component that provides device orientation data

193

* @example

194

* <UseDeviceOrientation v-slot="{ alpha, beta, gamma, absolute }">

195

* <div>Heading: {{ alpha }}° Tilt: {{ beta }}°, {{ gamma }}°</div>

196

* </UseDeviceOrientation>

197

*/

198

interface UseDeviceOrientationProps {

199

// No props - uses browser DeviceOrientation API directly

200

}

201

202

/** Slot data exposed by UseDeviceOrientation component */

203

interface DeviceOrientationState {

204

/** Compass heading (0-360°) */

205

alpha: Ref<number | null>;

206

/** Front-to-back tilt (-180 to 180°) */

207

beta: Ref<number | null>;

208

/** Left-to-right tilt (-90 to 90°) */

209

gamma: Ref<number | null>;

210

/** Whether orientation is absolute (true north) */

211

absolute: Ref<boolean>;

212

/** Whether the DeviceOrientation API is supported */

213

isSupported: Ref<boolean>;

214

}

215

```

216

217

**Usage Examples:**

218

219

```vue

220

<template>

221

<!-- Basic orientation -->

222

<UseDeviceOrientation v-slot="{ alpha, beta, gamma, absolute, isSupported }">

223

<div v-if="isSupported" class="orientation-display">

224

<h3>Device Orientation</h3>

225

<div class="compass" :style="{ transform: `rotate(${alpha || 0}deg)` }">

226

<div class="compass-needle"></div>

227

</div>

228

<p>Compass: {{ alpha?.toFixed(1) ?? 'N/A' }}° {{ absolute ? '(True North)' : '(Magnetic)' }}</p>

229

<p>Pitch: {{ beta?.toFixed(1) ?? 'N/A' }}°</p>

230

<p>Roll: {{ gamma?.toFixed(1) ?? 'N/A' }}°</p>

231

</div>

232

<div v-else>Device orientation not supported</div>

233

</UseDeviceOrientation>

234

235

<!-- Tilt indicator -->

236

<UseDeviceOrientation v-slot="{ beta, gamma }">

237

<div class="tilt-indicator">

238

<div

239

class="tilt-ball"

240

:style="{

241

transform: `translateX(${(gamma || 0) * 2}px) translateY(${(beta || 0) * 2}px)`

242

}"

243

></div>

244

<p>Tilt: {{ beta?.toFixed(1) ?? 'N/A' }}°, {{ gamma?.toFixed(1) ?? 'N/A' }}°</p>

245

</div>

246

</UseDeviceOrientation>

247

</template>

248

249

<script setup>

250

import { UseDeviceOrientation } from '@vueuse/components';

251

</script>

252

253

<style>

254

.compass {

255

width: 100px;

256

height: 100px;

257

border: 2px solid #333;

258

border-radius: 50%;

259

position: relative;

260

margin: 20px auto;

261

}

262

263

.compass-needle {

264

position: absolute;

265

top: 10%;

266

left: 50%;

267

width: 2px;

268

height: 40%;

269

background: red;

270

transform: translateX(-50%);

271

transform-origin: bottom center;

272

}

273

274

.tilt-indicator {

275

width: 200px;

276

height: 200px;

277

border: 2px solid #333;

278

border-radius: 10px;

279

position: relative;

280

margin: 20px auto;

281

overflow: hidden;

282

}

283

284

.tilt-ball {

285

position: absolute;

286

top: 50%;

287

left: 50%;

288

width: 20px;

289

height: 20px;

290

background: #2196f3;

291

border-radius: 50%;

292

transform: translate(-50%, -50%);

293

transition: transform 0.1s ease;

294

}

295

</style>

296

```

297

298

### UseDevicePixelRatio Component

299

300

Tracks device pixel ratio changes.

301

302

```typescript { .api }

303

/**

304

* Component that tracks device pixel ratio

305

* @example

306

* <UseDevicePixelRatio v-slot="{ pixelRatio }">

307

* <div>Pixel Ratio: {{ pixelRatio }}</div>

308

* </UseDevicePixelRatio>

309

*/

310

interface UseDevicePixelRatioProps {

311

// No props - uses browser devicePixelRatio API

312

}

313

314

/** Slot data exposed by UseDevicePixelRatio component */

315

interface UseDevicePixelRatioReturn {

316

/** Current device pixel ratio */

317

pixelRatio: Ref<number>;

318

}

319

```

320

321

**Usage Examples:**

322

323

```vue

324

<template>

325

<!-- Basic pixel ratio -->

326

<UseDevicePixelRatio v-slot="{ pixelRatio }">

327

<div class="pixel-ratio-info">

328

<h3>Display Information</h3>

329

<p>Device Pixel Ratio: {{ pixelRatio }}</p>

330

<p>Display Type: {{ getDisplayType(pixelRatio) }}</p>

331

</div>

332

</UseDevicePixelRatio>

333

334

<!-- Responsive images based on pixel ratio -->

335

<UseDevicePixelRatio v-slot="{ pixelRatio }">

336

<img

337

:src="getImageSrc(pixelRatio)"

338

:alt="`Image for ${pixelRatio}x display`"

339

class="responsive-image"

340

/>

341

</UseDevicePixelRatio>

342

</template>

343

344

<script setup>

345

import { UseDevicePixelRatio } from '@vueuse/components';

346

347

function getDisplayType(ratio) {

348

if (ratio >= 3) return 'Ultra High DPI';

349

if (ratio >= 2) return 'High DPI (Retina)';

350

if (ratio >= 1.5) return 'Medium DPI';

351

return 'Standard DPI';

352

}

353

354

function getImageSrc(ratio) {

355

if (ratio >= 2) return '/images/logo@2x.png';

356

return '/images/logo.png';

357

}

358

</script>

359

```

360

361

### UseDevicesList Component

362

363

Lists available media devices (cameras, microphones, speakers).

364

365

```typescript { .api }

366

/**

367

* Component that lists available media devices

368

* @example

369

* <UseDevicesList v-slot="{ videoInputs, audioInputs, audioOutputs }">

370

* <div>Cameras: {{ videoInputs.length }}, Mics: {{ audioInputs.length }}</div>

371

* </UseDevicesList>

372

*/

373

interface UseDevicesListProps {

374

/** Request permissions for device access @default false */

375

requestPermissions?: boolean;

376

/** Device constraints for permission request */

377

constraints?: MediaStreamConstraints;

378

/** Device change callback */

379

onUpdated?: (devices: MediaDeviceInfo[]) => void;

380

}

381

382

/** Slot data exposed by UseDevicesList component */

383

interface UseDevicesListReturn {

384

/** All available devices */

385

devices: Ref<MediaDeviceInfo[]>;

386

/** Video input devices (cameras) */

387

videoInputs: Ref<MediaDeviceInfo[]>;

388

/** Audio input devices (microphones) */

389

audioInputs: Ref<MediaDeviceInfo[]>;

390

/** Audio output devices (speakers) */

391

audioOutputs: Ref<MediaDeviceInfo[]>;

392

/** Whether the MediaDevices API is supported */

393

isSupported: Ref<boolean>;

394

/** Permissions granted status */

395

permissionGranted: Ref<boolean>;

396

/** Refresh device list */

397

ensurePermissions: () => Promise<boolean>;

398

/** Update device list */

399

update: () => Promise<void>;

400

}

401

402

interface MediaDeviceInfo {

403

deviceId: string;

404

kind: 'videoinput' | 'audioinput' | 'audiooutput';

405

label: string;

406

groupId: string;

407

}

408

409

interface MediaStreamConstraints {

410

video?: boolean | MediaTrackConstraints;

411

audio?: boolean | MediaTrackConstraints;

412

}

413

```

414

415

**Usage Examples:**

416

417

```vue

418

<template>

419

<!-- Basic device list -->

420

<UseDevicesList v-slot="{ videoInputs, audioInputs, audioOutputs, isSupported }">

421

<div v-if="isSupported" class="devices-list">

422

<h3>Available Devices</h3>

423

424

<div class="device-category">

425

<h4>Cameras ({{ videoInputs.length }})</h4>

426

<ul>

427

<li v-for="device in videoInputs" :key="device.deviceId">

428

{{ device.label || `Camera ${device.deviceId.slice(0, 8)}...` }}

429

</li>

430

</ul>

431

</div>

432

433

<div class="device-category">

434

<h4>Microphones ({{ audioInputs.length }})</h4>

435

<ul>

436

<li v-for="device in audioInputs" :key="device.deviceId">

437

{{ device.label || `Microphone ${device.deviceId.slice(0, 8)}...` }}

438

</li>

439

</ul>

440

</div>

441

442

<div class="device-category">

443

<h4>Speakers ({{ audioOutputs.length }})</h4>

444

<ul>

445

<li v-for="device in audioOutputs" :key="device.deviceId">

446

{{ device.label || `Speaker ${device.deviceId.slice(0, 8)}...` }}

447

</li>

448

</ul>

449

</div>

450

</div>

451

<div v-else>Media devices not supported</div>

452

</UseDevicesList>

453

454

<!-- With permissions -->

455

<UseDevicesList

456

:request-permissions="true"

457

:constraints="{ video: true, audio: true }"

458

v-slot="{ devices, permissionGranted, ensurePermissions, update }"

459

>

460

<div class="permissions-devices">

461

<div v-if="!permissionGranted" class="permission-request">

462

<p>Camera and microphone access required</p>

463

<button @click="ensurePermissions">Grant Permissions</button>

464

</div>

465

<div v-else class="devices-granted">

466

<p>Found {{ devices.length }} devices</p>

467

<button @click="update">Refresh List</button>

468

<div v-for="device in devices" :key="device.deviceId" class="device-item">

469

<span class="device-type">{{ device.kind }}</span>

470

<span class="device-label">{{ device.label }}</span>

471

</div>

472

</div>

473

</div>

474

</UseDevicesList>

475

</template>

476

477

<script setup>

478

import { UseDevicesList } from '@vueuse/components';

479

</script>

480

481

<style>

482

.device-category {

483

margin: 15px 0;

484

}

485

486

.device-category h4 {

487

margin: 5px 0;

488

color: #666;

489

}

490

491

.device-category ul {

492

list-style: none;

493

padding: 0;

494

}

495

496

.device-category li {

497

padding: 5px 0;

498

border-bottom: 1px solid #eee;

499

}

500

501

.device-item {

502

display: flex;

503

justify-content: space-between;

504

padding: 8px;

505

border: 1px solid #ddd;

506

margin: 5px 0;

507

border-radius: 4px;

508

}

509

510

.device-type {

511

font-weight: bold;

512

text-transform: capitalize;

513

}

514

515

.permission-request {

516

text-align: center;

517

padding: 20px;

518

border: 2px dashed #ccc;

519

border-radius: 8px;

520

}

521

</style>

522

```

523

524

## Type Definitions

525

526

```typescript { .api }

527

/** Common types used across device and sensor components */

528

type MaybeRefOrGetter<T> = T | Ref<T> | (() => T);

529

530

interface RenderableComponent {

531

/** The element that the component should be rendered as @default 'div' */

532

as?: object | string;

533

}

534

535

/** Battery API types */

536

interface BatteryManager {

537

charging: boolean;

538

chargingTime: number;

539

dischargingTime: number;

540

level: number;

541

onchargingchange: ((this: BatteryManager, ev: Event) => any) | null;

542

onchargingtimechange: ((this: BatteryManager, ev: Event) => any) | null;

543

ondischargingtimechange: ((this: BatteryManager, ev: Event) => any) | null;

544

onlevelchange: ((this: BatteryManager, ev: Event) => any) | null;

545

}

546

547

/** Device motion and orientation types */

548

interface DeviceMotionEvent extends Event {

549

acceleration: DeviceMotionAcceleration | null;

550

accelerationIncludingGravity: DeviceMotionAcceleration | null;

551

rotationRate: DeviceMotionRotationRate | null;

552

interval: number;

553

}

554

555

interface DeviceOrientationEvent extends Event {

556

alpha: number | null;

557

beta: number | null;

558

gamma: number | null;

559

absolute: boolean;

560

}

561

```