Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .changeset/vibrate-initial.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
"@solid-primitives/vibrate": minor
---

Add `@solid-primitives/vibrate` package (Stage 0)

New primitives for device haptic feedback via the [Vibration API](https://developer.mozilla.org/en-US/docs/Web/API/Vibration_API).

- **`isVibrationSupported()`** — runtime check for Vibration API availability.
- **`makeVibrate(pattern, options?)`** — non-reactive helper returning `[start, stop]`. Supports optional `interval` for repeating patterns. No-ops when the API is unavailable.
- **`createVibrate(pattern, options?)`** — reactive primitive returning `{ vibrating, start, stop, supported }`. Accepts a reactive accessor for `pattern`: changing it while vibrating restarts automatically. Stops and cleans up on owner disposal.
- **`frequencyToPattern(hz, dutyCycle?)`** — converts a frequency in Hz to a single-cycle `[onMs, offMs]` pattern.
- **`makePulse(hz, options?)`** — non-reactive helper that vibrates continuously at `hz` cycles per second using a repeating chunk strategy.
- **`createPulse(hz, options?)`** — reactive pulse primitive returning `{ pulsing, start, stop, supported }`. Accepts a reactive `hz` accessor: changing frequency while pulsing restarts immediately at the new rhythm.

Peer dependencies: `solid-js@^2.0.0-beta.10` and `@solidjs/web@^2.0.0-beta.10`.
21 changes: 21 additions & 0 deletions packages/vibrate/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2021 Solid Primitives Working Group

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
167 changes: 167 additions & 0 deletions packages/vibrate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
<p>
<img width="100%" src="https://assets.solidjs.com/banner?type=Primitives&background=tiles&project=vibrate" alt="Solid Primitives vibrate">
</p>

# @solid-primitives/vibrate

[![size](https://img.shields.io/bundlephobia/minzip/@solid-primitives/vibrate?style=for-the-badge&label=size)](https://bundlephobia.com/package/@solid-primitives/vibrate)
[![version](https://img.shields.io/npm/v/@solid-primitives/vibrate?style=for-the-badge)](https://www.npmjs.com/package/@solid-primitives/vibrate)
[![stage](https://img.shields.io/endpoint?style=for-the-badge&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-0.json)](https://github.com/solidjs-community/solid-primitives#contribution-process)

Primitives for triggering and managing device haptic feedback via the [Vibration API](https://developer.mozilla.org/en-US/docs/Web/API/Vibration_API).

- [`isVibrationSupported`](#isvibrationSupported) — Check if the Vibration API is available.
- [`makeVibrate`](#makevibrate) — Non-reactive helper; returns `[start, stop]` with no Solid lifecycle dependency.
- [`createVibrate`](#createvibrate) — Reactive primitive; returns `{ vibrating, start, stop, supported }` with automatic cleanup and reactive pattern support.
- [`frequencyToPattern`](#frequencytopattern) — Convert a frequency in Hz to a `[onMs, offMs]` pattern.
- [`makePulse`](#makepulse) — Non-reactive pulse helper; vibrates continuously at a given frequency.
- [`createPulse`](#createpulse) — Reactive pulse primitive; supports reactive `hz` that restarts on change.

## Installation

```bash
npm install @solid-primitives/vibrate
# or
yarn add @solid-primitives/vibrate
# or
pnpm add @solid-primitives/vibrate
```

## `isVibrationSupported`

Returns `true` when the [Vibration API](https://developer.mozilla.org/en-US/docs/Web/API/Vibration_API) is available. Useful for conditionally rendering haptic UI or showing fallback content.

```ts
import { isVibrationSupported } from "@solid-primitives/vibrate";

if (isVibrationSupported()) {
console.log("haptics available");
}
```

## `makeVibrate`

A non-reactive building block. Wraps `navigator.vibrate` with an optional repeating interval and returns `[start, stop]`. No Solid lifecycle dependency — both functions are no-ops when the API is unavailable.

```ts
import { makeVibrate } from "@solid-primitives/vibrate";

// Single-shot vibration
const [start, stop] = makeVibrate([200, 100, 200]);
button.addEventListener("click", start);

// Repeating vibration every 2 seconds
const [start, stop] = makeVibrate(100, { interval: 2000 });
start();
// later:
stop();
```

## `createVibrate`

A reactive primitive tied to the current reactive owner. Returns `{ vibrating, start, stop, supported }`. Cleans up automatically on owner disposal.

When `pattern` is a reactive accessor and changes **while vibrating**, vibration restarts with the new pattern automatically.

```ts
import { createVibrate } from "@solid-primitives/vibrate";
import { createEffect } from "solid-js";

const { vibrating, start, stop, supported } = createVibrate([200, 100, 200]);

createEffect(() => {
console.log("vibrating:", vibrating());
});
```

### Reactive pattern

```ts
import { createVibrate } from "@solid-primitives/vibrate";
import { createSignal } from "solid-js";

const [pattern, setPattern] = createSignal<number | number[]>(200);
const { vibrating, start, stop } = createVibrate(pattern);

start();

// Changing pattern while vibrating restarts automatically
setPattern([100, 30, 100, 30, 100]);
```

### With interval

```ts
const { vibrating, start, stop } = createVibrate(200, { interval: 1000 });
start(); // repeats every second
stop(); // cancels interval + active vibration
```

## `frequencyToPattern`

Converts a frequency in Hz and an optional duty cycle into a single-cycle `[onMs, offMs]` vibration pattern. Useful for previewing what `makePulse` / `createPulse` will produce.

```ts
import { frequencyToPattern } from "@solid-primitives/vibrate";

frequencyToPattern(2) // [250, 250] — 2 Hz, equal on/off
frequencyToPattern(4, 0.25) // ~[63, 188] — 4 Hz, short tap
```

## `makePulse`

Non-reactive pulse helper. Vibrates continuously at `hz` cycles per second. No Solid lifecycle dependency; both functions are no-ops when the API is unavailable.

```ts
import { makePulse } from "@solid-primitives/vibrate";

const [start, stop] = makePulse(4); // 4 taps per second
button.addEventListener("pointerdown", start);
button.addEventListener("pointerup", stop);
```

## `createPulse`

Reactive pulse primitive tied to the current reactive owner. Returns `{ pulsing, start, stop, supported }`. Accepts a reactive `hz` accessor — changing the frequency while pulsing restarts the vibration immediately at the new rhythm.

```ts
import { createPulse } from "@solid-primitives/vibrate";
import { createSignal, createMemo } from "solid-js";

// Fixed frequency
const { pulsing, start, stop } = createPulse(2);

// Reactive frequency — escalates as a countdown nears zero
const [seconds, setSeconds] = createSignal(10);
const hz = createMemo(() => 1 + (10 - seconds()) * 0.5);
const { start, stop } = createPulse(hz);
```

## Types

```ts
export type VibratePattern = number | number[];

export interface VibrateOptions {
/** Milliseconds between pattern repetitions. Omit to vibrate once per call. */
interval?: number;
}

export interface PulseOptions {
/**
* Fraction of each cycle spent vibrating (0–1). Defaults to `0.5`.
* A higher value produces longer buzzes; a lower value produces shorter taps.
*/
dutyCycle?: number;
}
```

## Browser Support

The Vibration API is supported on Chrome/Firefox for Android. It is **not** supported on iOS or most desktop browsers. Always check `isVibrationSupported()` or rely on `supported` from `createVibrate` before enabling haptic features in your UI.

> Note: vibration requires a prior user interaction (sticky activation) and may be suppressed by silent/DND mode.

## Changelog

See [CHANGELOG.md](./CHANGELOG.md)
34 changes: 34 additions & 0 deletions packages/vibrate/dev/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { type Component, createSignal } from "solid-js";
import { createVibrate, isVibrationSupported } from "../src/index.js";

const App: Component = () => {
const [pattern, setPattern] = createSignal<number | number[]>([200, 100, 200]);
const { vibrating, start, stop, supported } = createVibrate(pattern);

return (
<div class="box-border flex min-h-screen w-full flex-col items-center justify-center space-y-4 bg-gray-800 p-24 text-white">
<div class="wrapper-v">
<h4>Vibration</h4>
<p class="caption">
{supported ? "Vibration API supported" : "Vibration API not supported in this browser"}
</p>
<p>
<strong>Status:</strong> {vibrating() ? "Vibrating" : "Idle"}
</p>
<div class="flex gap-4">
<button class="btn" onClick={start} disabled={!supported}>
Start
</button>
<button class="btn" onClick={stop} disabled={!supported}>
Stop
</button>
</div>
<p class="caption">
Supported: {String(isVibrationSupported())}
</p>
</div>
</div>
);
};

export default App;
71 changes: 71 additions & 0 deletions packages/vibrate/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"name": "@solid-primitives/vibrate",
"version": "0.0.100",
"description": "Primitives to trigger and manage device vibration via the Vibration API",
"author": "David Di Biase <dave@solidjs.com>",
"contributors": [],
"license": "MIT",
"homepage": "https://primitives.solidjs.community/package/vibrate",
"repository": {
"type": "git",
"url": "git+https://github.com/solidjs-community/solid-primitives.git"
},
"bugs": {
"url": "https://github.com/solidjs-community/solid-primitives/issues"
},
"primitive": {
"name": "vibrate",
"stage": 0,
"list": [
"isVibrationSupported",
"makeVibrate",
"createVibrate",
"frequencyToPattern",
"makePulse",
"createPulse"
],
"category": "Sensors"
},
"keywords": [
"solid",
"vibrate",
"vibration",
"haptic",
"primitives"
],
"private": false,
"sideEffects": false,
"files": [
"dist"
],
"type": "module",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"browser": {},
"exports": {
"import": {
"@solid-primitives/source": "./src/index.ts",
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"typesVersions": {},
"scripts": {
"dev": "node --import=@nothing-but/node-resolve-ts --experimental-transform-types ../../scripts/dev.ts",
"build": "node --import=@nothing-but/node-resolve-ts --experimental-transform-types ../../scripts/build.ts",
"vitest": "vitest -c ../../configs/vitest.config.ts",
"test": "pnpm run vitest",
"test:ssr": "pnpm run vitest --mode ssr"
},
"peerDependencies": {
"@solidjs/web": "^2.0.0-beta.10",
"solid-js": "^2.0.0-beta.10"
},
"dependencies": {
"@solid-primitives/utils": "workspace:^"
},
"devDependencies": {
"@solidjs/web": "2.0.0-beta.10",
"solid-js": "2.0.0-beta.10"
}
}
Loading
Loading