npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-haptic-composer

v0.1.0

Published

Declarative haptic orchestration for React Native — compose iOS CoreHaptics and Android VibratorManager patterns with a tiny string DSL, presets, and a testable engine abstraction.

Readme

react-native-haptic-composer

Declarative haptic orchestration for React Native. Compose iOS CoreHaptics and Android VibratorManager patterns with a tiny string DSL, trigger them from one hook, and test everything with a recording mock engine — no device required.

const { trigger } = useHapticComposer();
await trigger('short-heavy-pause-100-long-light-rise'); // or trigger('heartbeat')

Features

  • Pattern DSL — describe timings, intensities, transient taps, pauses and intensity ramps in one readable string.
  • Pure, typed parserparsePattern() has zero react-native imports and throws a typed HapticPatternError with the offending token index.
  • Bundled presetssuccess-celebration, heartbeat, error-crunch, plus soft-tick and drumroll.
  • Engine abstraction — CoreHaptics (iOS) / VibratorManager (Android) via a small native module, with an automatic Vibration fallback when the native side isn't installed (Expo Go, quick prototypes).
  • First-class testability — inject MockHapticEngine to record exactly what would have played.
  • Zero runtime dependencies.

Install

npm install react-native-haptic-composer
# or
yarn add react-native-haptic-composer

Peer dependencies: react >= 18, react-native >= 0.72.

Native setup (optional but recommended)

Out of the box the library plays patterns through React Native's Vibration API — rhythm only. To get real intensity, sharpness and ramps, install the native HapticComposer module from native-setup/:

  • iOS (CoreHaptics) — copy native-setup/ios/HapticComposerModule.swift plus a small Objective-C export shim into your app; needs iOS 13+ and a device with a Taptic Engine. Full steps: native-setup/ios/README.md.
  • Android (VibratorManager) — copy native-setup/android/HapticComposerModule.kt plus a ReactPackage, add the VIBRATE permission. Amplitude control needs API 26+; API 31+ uses VibratorManager. Full steps: native-setup/android/README.md.

The hook auto-detects the module: engineKind is 'core-haptics' / 'vibrator-manager' when it's installed, 'rn-vibration-fallback' otherwise.

Quickstart

import { Button } from 'react-native';
import { useHapticComposer } from 'react-native-haptic-composer';

export function LikeButton() {
  const { trigger, stop, engineKind, isSupported } = useHapticComposer();

  return (
    <Button
      title={`Like (${engineKind}${isSupported ? '' : ', unsupported'})`}
      onPress={() => {
        // Preset name, or any DSL string:
        trigger('success-celebration').catch(console.error);
      }}
      onLongPress={stop}
    />
  );
}

A complete screen with a button per preset and a custom-pattern composer lives in example/HapticButtonsScreen.tsx.

Pattern DSL

Patterns are dash-separated tokens, case-insensitive. A pulse is a duration word plus an optional intensity word, in either order (short-heavy == heavy-short). Without an intensity word a pulse defaults to soft (0.6).

| Token | Meaning | Values | | --- | --- | --- | | short | continuous pulse | 60 ms, sharpness 0.8 | | medium | continuous pulse | 150 ms, sharpness 0.5 | | long | continuous pulse | 300 ms, sharpness 0.3 | | tap | transient micro-pulse | 10 ms, sharpness 1.0, default intensity 1.0 | | light | intensity for the adjacent pulse | 0.3 | | soft | intensity for the adjacent pulse | 0.6 | | heavy | intensity for the adjacent pulse | 1.0 | | pause-<ms> | silence | e.g. pause-100 | | <number> | bare-number shorthand for a pause | e.g. 120 | | rise / fall | intensity ramp on the preceding continuous pulse | not valid on tap |

Examples:

| Pattern | Result | | --- | --- | | tap | one crisp 10 ms tick | | short-heavy | 60 ms pulse at full intensity | | light-long-rise | 300 ms gentle pulse swelling upward | | tap-40-tap-40-tap | three ticks, 40 ms apart | | short-heavy-pause-100-long-light-rise | heavy blip, 100 ms rest, long rising light pulse |

parsePattern(pattern) returns { steps, totalDurationMs }, where each step carries an absolute atMs offset, durationMs, intensity, sharpness, transient, and an optional curve. Malformed input throws HapticPatternError with tokenIndex and token.

Presets

| Name | Pattern | Feel | | --- | --- | --- | | success-celebration | tap-pause-80-tap-pause-80-long-heavy-rise | two ticks, then a rising swell | | heartbeat | medium-heavy-pause-120-medium-soft-pause-600 | lub-dub with a rest (loop-friendly) | | error-crunch | short-heavy-pause-40-short-heavy-pause-40-long-heavy-fall | harsh double buzz dying away | | soft-tick | tap-light | single gentle tick | | drumroll | tap-40-tap-40-tap-40-tap-40-tap-pause-120-medium-heavy-rise | five taps into a rising accent |

trigger() resolves preset names first (case-insensitive); anything else is parsed as DSL.

API

useHapticComposer(options?)

useHapticComposer(options?: { engine?: HapticEngine }): {
  trigger(patternOrPreset: string): Promise<void>;
  stop(): void;
  engineKind: 'core-haptics' | 'vibrator-manager' | 'rn-vibration-fallback' | 'mock';
  isSupported: boolean; // async capability check; starts false, settles after mount
}

Core

  • parsePattern(pattern: string): HapticPattern — pure DSL parser.
  • HapticPatternError — typed parse error (tokenIndex, token).
  • HAPTIC_PRESETS, isHapticPresetName() — bundled presets.
  • Types: HapticPattern, HapticStep, HapticCurve, HapticEngineKind, HapticPresetName.

Engines

  • HapticEngine{ play(pattern): Promise<void>; stop(): void; kind; isSupported(): Promise<boolean> }.
  • NativeHapticEngine — drives NativeModules.HapticComposer.
  • VibrationFallbackEngine / toVibrationPattern(pattern) — converts steps to the [wait, vibrate, ...] ms array for RN Vibration.vibrate.
  • MockHapticEngine — records playback for tests.
  • getHapticComposerNativeModule() — shape-validated native module lookup.
  • resolveDefaultHapticEngine() — the hook's default selection logic.

Testing with MockHapticEngine

Inject the mock through the hook (or use it directly) to assert on exactly what would have played:

import { renderHook, act } from '@testing-library/react';
import {
  MockHapticEngine,
  parsePattern,
  useHapticComposer,
} from 'react-native-haptic-composer';

it('plays a heavy tick on save', async () => {
  const engine = new MockHapticEngine();
  const { result } = renderHook(() => useHapticComposer({ engine }));

  await act(async () => {
    await result.current.trigger('tap-heavy');
  });

  expect(engine.played).toHaveLength(1);
  expect(engine.lastPlayed).toEqual(parsePattern('tap-heavy'));
  expect(engine.stopCount).toBe(0);
});

MockHapticEngine exposes played (all patterns, oldest first), lastPlayed, stopCount, a mutable supported flag for simulating unsupported devices, and reset() for beforeEach.

Limitations of the Vibration fallback

  • Intensity, sharpness and curves are collapsed — the OS only receives on/off timings.
  • On iOS, Vibration.vibrate ignores per-element durations; only the rhythm survives. Install the native module for real CoreHaptics output.

License

MIT © Dinesh Kumar