← All @molecule/* packages · App templates

@molecule/app-motion

Native · native · App (browser) · v1.0.1 · Apache-2.0

Accelerometer and gyroscope interface for molecule.dev

npm install @molecule/app-motion

npm · Source on GitHub

How it works

@molecule/app-motion bridges the native core to the native platform layer of the app.

import {
  createShakeDetector,
  getAccelerometer,
  hasProvider,
  requestPermission,
} from '@molecule/app-motion'

async function undoOnShake(undo: () => void): Promise<() => void> {
  if (!hasProvider()) return () => {} // no provider wired — skip
  if ((await requestPermission()) !== 'granted') return () => {}
  const shake = createShakeDetector(undo) // self-wires the accelerometer
  shake.start()
  return () => shake.stop() // ALWAYS stop on unmount — sensors drain battery
}

async function logTilt(): Promise<void> {
  const { x, y, z } = await getAccelerometer() // one-off read
  console.log(x, y, z)
}

Works with: @molecule/app-bond, @molecule/app-i18n

Reference

Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit src/index.ts JSDoc, not this file.

Accelerometer and gyroscope interface for molecule.dev.

Framework-agnostic core for motion sensors through a swappable MotionProvider: continuous streams (startAccelerometer, startGyroscope, startMagnetometer, startOrientation, startMotion), one-off reads (getAccelerometer, getGyroscope, getOrientation), a permission flow, and pure gesture helpers (createShakeDetector, createTiltDetector, createStepCounter, vector math).

Quick Start

import {
  createShakeDetector,
  getAccelerometer,
  hasProvider,
  requestPermission,
} from '@molecule/app-motion'

async function undoOnShake(undo: () => void): Promise<() => void> {
  if (!hasProvider()) return () => {} // no provider wired — skip
  if ((await requestPermission()) !== 'granted') return () => {}
  const shake = createShakeDetector(undo) // self-wires the accelerometer
  shake.start()
  return () => shake.stop() // ALWAYS stop on unmount — sensors drain battery
}

async function logTilt(): Promise<void> {
  const { x, y, z } = await getAccelerometer() // one-off read
  console.log(x, y, z)
}

Type

native

Installation

npm install @molecule/app-motion @molecule/app-bond @molecule/app-i18n

API

Interfaces

AccelerometerData

Accelerometer data

interface AccelerometerData extends Vector3D {
  /** Timestamp */
  timestamp: number
  /** Whether gravity is included */
  includesGravity: boolean
}

GyroscopeData

Gyroscope rotation rate reading (x/y/z radians per second with timestamp).

interface GyroscopeData extends Vector3D {
  /** Timestamp */
  timestamp: number
}

MagnetometerData

Magnetometer data

interface MagnetometerData extends Vector3D {
  /** Timestamp */
  timestamp: number
}

MotionCapabilities

Motion capabilities

interface MotionCapabilities {
  /** Whether motion sensors are supported */
  supported: boolean
  /** Whether accelerometer is available */
  hasAccelerometer: boolean
  /** Whether gyroscope is available */
  hasGyroscope: boolean
  /** Whether magnetometer is available */
  hasMagnetometer: boolean
  /** Whether device orientation is available */
  hasOrientation: boolean
  /** Whether permission is required */
  requiresPermission: boolean
}

MotionData

Combined motion data

interface MotionData {
  /** Accelerometer with gravity */
  accelerationIncludingGravity?: AccelerometerData
  /** Accelerometer without gravity (linear) */
  acceleration?: AccelerometerData
  /** Gyroscope rotation rate */
  rotationRate?: GyroscopeData
  /** Device orientation */
  orientation?: OrientationData
  /** Interval in milliseconds */
  interval: number
  /** Timestamp */
  timestamp: number
}

MotionProvider

Motion provider interface

interface MotionProvider {
  /**
   * Start accelerometer updates
   * @param callback - Called with accelerometer data
   * @param options - Sensor options
   * @returns Stop function
   */
  startAccelerometer(
    callback: (data: AccelerometerData) => void,
    options?: SensorOptions,
  ): () => void

  /**
   * Start gyroscope updates
   * @param callback - Called with gyroscope data
   * @param options - Sensor options
   * @returns Stop function
   */
  startGyroscope(callback: (data: GyroscopeData) => void, options?: SensorOptions): () => void

  /**
   * Start magnetometer updates
   * @param callback - Called with magnetometer data
   * @param options - Sensor options
   * @returns Stop function
   */
  startMagnetometer(callback: (data: MagnetometerData) => void, options?: SensorOptions): () => void

  /**
   * Start device orientation updates
   * @param callback - Called with orientation data
   * @param options - Sensor options
   * @returns Stop function
   */
  startOrientation(callback: (data: OrientationData) => void, options?: SensorOptions): () => void

  /**
   * Start combined motion updates
   * @param callback - Called with motion data
   * @param options - Sensor options
   * @returns Stop function
   */
  startMotion(callback: (data: MotionData) => void, options?: SensorOptions): () => void

  /**
   * Get the current accelerometer reading.
   * @returns The current accelerometer data with x, y, z values and timestamp.
   */
  getAccelerometer(): Promise<AccelerometerData>

  /**
   * Get the current gyroscope reading.
   * @returns The current gyroscope data with x, y, z rotation rates and timestamp.
   */
  getGyroscope(): Promise<GyroscopeData>

  /**
   * Get the current device orientation reading.
   * @returns The current orientation data with alpha, beta, gamma angles and timestamp.
   */
  getOrientation(): Promise<OrientationData>

  /**
   * Get the motion sensor permission status.
   * @returns The permission status: 'granted', 'denied', 'prompt', or 'unsupported'.
   */
  getPermissionStatus(): Promise<MotionPermissionStatus>

  /**
   * Request motion sensor permission (required on iOS 13+).
   * @returns The resulting permission status after the request.
   */
  requestPermission(): Promise<MotionPermissionStatus>

  /**
   * Get the platform's motion sensor capabilities.
   * @returns The capabilities indicating which sensors are available.
   */
  getCapabilities(): Promise<MotionCapabilities>
}

OrientationData

Device orientation data

interface OrientationData {
  /** Alpha (rotation around Z-axis, 0-360) */
  alpha: number
  /** Beta (rotation around X-axis, -180 to 180) */
  beta: number
  /** Gamma (rotation around Y-axis, -90 to 90) */
  gamma: number
  /** Timestamp */
  timestamp: number
  /** Whether orientation is absolute */
  absolute: boolean
}

SensorOptions

Configuration for motion sensor listening (sampling frequency in Hz).

interface SensorOptions {
  /** Sampling frequency in Hz (default: 60) */
  frequency?: number
}

ShakeOptions

Shake detection options

interface ShakeOptions {
  /** Shake threshold acceleration (default: 15) */
  threshold?: number
  /** Minimum shakes to trigger (default: 3) */
  minShakes?: number
  /** Time window in ms (default: 1000) */
  timeWindow?: number
}

Vector3D

3D vector for sensor data

interface Vector3D {
  /** X-axis value */
  x: number
  /** Y-axis value */
  y: number
  /** Z-axis value */
  z: number
}

Types

MotionPermissionStatus

Motion permission status

type MotionPermissionStatus = 'granted' | 'denied' | 'prompt' | 'unsupported'

Functions

createShakeDetector(onShake, options)

Create a shake gesture detector that uses accelerometer data to detect device shaking.

function createShakeDetector(
  onShake: () => void,
  options?: ShakeOptions,
): { start: () => void; stop: () => void }
  • onShake — Called when a shake gesture is detected.
  • options — Shake detection options (threshold, minimum shakes, time window).

Returns: A controller with start and stop methods for the shake detector.

createStepCounter(onStep)

Create a basic step counter using accelerometer peak detection.

function createStepCounter(onStep: (count: number) => void): {
  start: () => void
  stop: () => void
  getCount: () => number
  reset: () => void
}
  • onStep — Called with the cumulative step count each time a step is detected.

Returns: A controller with start, stop, getCount, and reset methods.

createTiltDetector(onChange, options)

Create a tilt detector that calculates pitch and roll angles from accelerometer data.

function createTiltDetector(
  onChange: (tilt: { pitch: number; roll: number }) => void,
  options?: SensorOptions,
): { start: () => void; stop: () => void }
  • onChange — Called with pitch (front-back tilt) and roll (left-right tilt) angles in degrees.
  • options — Sensor options (sampling frequency).

Returns: A controller with start and stop methods for the tilt detector.

cross(a, b)

Calculate the cross product of two 3D vectors.

function cross(a: Vector3D, b: Vector3D): Vector3D
  • a — The first vector.
  • b — The second vector.

Returns: A new Vector3D perpendicular to both input vectors.

dot(a, b)

Calculate the dot product of two 3D vectors.

function dot(a: Vector3D, b: Vector3D): number
  • a — The first vector.
  • b — The second vector.

Returns: The scalar dot product (a.xb.x + a.yb.y + a.z*b.z).

getAccelerometer()

Get the current accelerometer reading.

function getAccelerometer(): Promise<AccelerometerData>

Returns: The current accelerometer data with x, y, z values and timestamp.

getCapabilities()

Get the platform's motion sensor capabilities.

function getCapabilities(): Promise<MotionCapabilities>

Returns: The capabilities indicating which sensors are available.

getGyroscope()

Get the current gyroscope reading.

function getGyroscope(): Promise<GyroscopeData>

Returns: The current gyroscope data with x, y, z rotation rates and timestamp.

getOrientation()

Get the current device orientation reading.

function getOrientation(): Promise<OrientationData>

Returns: The current orientation data with alpha, beta, gamma angles and timestamp.

getPermissionStatus()

Get the motion sensor permission status.

function getPermissionStatus(): Promise<MotionPermissionStatus>

Returns: The permission status: 'granted', 'denied', 'prompt', or 'unsupported'.

getProvider()

Get the current motion provider.

function getProvider(): MotionProvider

Returns: The active MotionProvider instance.

hasProvider()

Check if a motion provider has been registered.

function hasProvider(): boolean

Returns: Whether a MotionProvider has been bonded.

magnitude(v)

Calculate the magnitude (length) of a 3D vector.

function magnitude(v: Vector3D): number
  • v — The 3D vector with x, y, z components.

Returns: The Euclidean magnitude of the vector.

normalize(v)

Normalize a 3D vector to unit length. Returns a zero vector if the input has zero magnitude.

function normalize(v: Vector3D): Vector3D
  • v — The 3D vector to normalize.

Returns: A new Vector3D with unit length pointing in the same direction.

requestPermission()

Request motion sensor permission (required on iOS 13+).

function requestPermission(): Promise<MotionPermissionStatus>

Returns: The resulting permission status after the request.

setProvider(provider)

Set the motion provider.

function setProvider(provider: MotionProvider): void
  • provider — MotionProvider implementation to register.

startAccelerometer(callback, options)

Start receiving accelerometer data updates.

function startAccelerometer(
  callback: (data: AccelerometerData) => void,
  options?: SensorOptions,
): () => void
  • callback — Called with AccelerometerData on each sensor reading.
  • options — Sensor options (sampling frequency).

Returns: A function that stops the accelerometer updates when called.

startGyroscope(callback, options)

Start receiving gyroscope data updates.

function startGyroscope(
  callback: (data: GyroscopeData) => void,
  options?: SensorOptions,
): () => void
  • callback — Called with GyroscopeData on each sensor reading.
  • options — Sensor options (sampling frequency).

Returns: A function that stops the gyroscope updates when called.

startMagnetometer(callback, options)

Start receiving magnetometer data updates.

function startMagnetometer(
  callback: (data: MagnetometerData) => void,
  options?: SensorOptions,
): () => void
  • callback — Called with MagnetometerData on each sensor reading.
  • options — Sensor options (sampling frequency).

Returns: A function that stops the magnetometer updates when called.

startMotion(callback, options)

Start receiving combined motion data (accelerometer, gyroscope, orientation).

function startMotion(callback: (data: MotionData) => void, options?: SensorOptions): () => void
  • callback — Called with combined MotionData on each reading.
  • options — Sensor options (sampling frequency).

Returns: A function that stops the motion updates when called.

startOrientation(callback, options)

Start receiving device orientation updates.

function startOrientation(
  callback: (data: OrientationData) => void,
  options?: SensorOptions,
): () => void
  • callback — Called with OrientationData (alpha, beta, gamma angles) on each reading.
  • options — Sensor options (sampling frequency).

Returns: A function that stops the orientation updates when called.

Injection Notes

Requirements

Peer dependencies:

  • @molecule/app-bond ^1.0.1
  • @molecule/app-i18n ^1.0.1

Runtime Dependencies

  • @molecule/app-bond

  • @molecule/app-i18n

  • Every accessor THROWS until setProvider() is called — there is no web fallback and no prebuilt provider package ships with molecule; supply a MotionProvider (native runtime, or a thin web one over devicemotion/deviceorientation events).

  • iOS WebKit requires requestPermission() from a USER GESTURE (DeviceMotionEvent.requestPermission) on HTTPS — calling it on page load, or on http, rejects without a prompt. Desktop browsers simply have no sensors: treat 'unsupported' as a normal outcome, not an error.

  • Every start* returns a stop function — call it on unmount; a leaked 60 Hz sensor stream is a battery drain and keeps the page from sleeping.

  • Axes/units are normalized by the provider contract (m/s², rad/s), but includesGravity differs per source — check the flag on AccelerometerData before applying filters.

Translations

Translation strings are provided by @molecule/app-locales-motion.