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

@mxerf/tappt

v0.1.4

Published

Tiny, SSR-safe haptic feedback for the web. Works in Telegram Mini Apps, iOS Safari 17.4+ (Taptic Engine via the switch hack), and anywhere the Vibration API is available. Framework adapters for React and Vue.

Readme

tappt

Read in English

Маленькая SSR-безопасная библиотека тактильной отдачи для веба. Работает в Telegram Mini Apps, iOS Safari 17.4+ (через Taptic Engine и «switch»-хак) и везде, где доступен Vibration API. На неподдерживаемых платформах молча ничего не делает.

  • ~1.4 КБ min+gzip в core, нулевые runtime-зависимости
  • Ленивая feature-детекция, без user-agent sniffing
  • Встроенные адаптеры для React и Vue 3
  • TypeScript-first, поставляется в ESM + CJS + .d.ts
  • SSR-безопасно: импорт на сервере не трогает document

Зачем

Возможностей для тактильной отдачи в вебе немного, и они разрозненны:

  1. Telegram Mini Apps предоставляют window.Telegram.WebApp.HapticFeedback с полноценным API impact / notification / selection.
  2. iOS Safari 17.4+ получил элемент <input type="checkbox" switch>: при переключении он вызывает настоящий импульс Taptic Engine. Это единственный способ запустить нативную вибрацию в iOS-PWA — публичного API для этого нет, а navigator.vibrate на iOS не реализован.
  3. Android и остальные браузеры предоставляют navigator.vibrate(pattern).

При первом вызове tappt выбирает лучший доступный бэкенд и даёт единый стабильный API.

Установка

bun add @mxerf/tappt
# или
npm i @mxerf/tappt
# или
pnpm add @mxerf/tappt

React и Vue объявлены как опциональные peer-зависимости — они нужны только если ты импортируешь @mxerf/tappt/react или @mxerf/tappt/vue.

Использование

Vanilla

import { haptic } from "@mxerf/tappt";

button.addEventListener("click", () => haptic.impact("medium"));
form.addEventListener("submit", () => haptic.notify("success"));
tabs.addEventListener("change", () => haptic.selection());

Либо именованные функции напрямую:

import { impact, notify, selection } from "@mxerf/tappt";

impact("light");
notify("error");
selection();

Discriminated-событие

Удобно, когда нужно пробросить тип haptic-отклика через пропсы компонента:

import { trigger, type HapticEvent } from "@mxerf/tappt";

function handleAction(event: HapticEvent) {
  trigger(event);
}

handleAction({ kind: "impact", style: "heavy" });
handleAction({ kind: "notification", type: "warning" });
handleAction({ kind: "selection" });

Scoped-инстансы

Для тестов, локальных opt-out или принудительного выбора бэкенда:

import { createHaptic } from "@mxerf/tappt";

const haptic = createHaptic({
  backend: "vibration", // форсировать бэкенд
  disabled: false,       // true → все вызовы становятся no-op
});

haptic.impact();
haptic.destroy(); // освобождает iOS-rig и внутреннее состояние

React

import { useHaptic, TapptProvider } from "@mxerf/tappt/react";

function LikeButton() {
  const haptic = useHaptic();
  return <button onClick={() => haptic.impact("medium")}>Лайк</button>;
}

// По желанию: отдельный инстанс для поддерева
export function App() {
  return (
    <TapptProvider options={{ disabled: userPrefersNoHaptics }}>
      <LikeButton />
    </TapptProvider>
  );
}

Без TapptProvider хук useHaptic() возвращает общий синглтон на уровне модуля — никакой настройки не требуется.

Vue 3

<script setup lang="ts">
import { useHaptic } from "@mxerf/tappt/vue";

const haptic = useHaptic();
</script>

<template>
  <button @click="haptic.impact('medium')">Лайк</button>
</template>

Подключить плагином, чтобы зарегистрировать общий экземпляр на всё приложение:

import { createApp } from "vue";
import { tapptPlugin } from "@mxerf/tappt/vue";

createApp(App).use(tapptPlugin({ disabled: false })).mount("#app");

Или создать инстанс, привязанный к компоненту — он автоматически уничтожится на unmount:

const haptic = useHaptic({ scoped: true, options: { backend: "vibration" } });

API

Типы

type ImpactStyle = "light" | "medium" | "heavy" | "rigid" | "soft";
type NotificationType = "success" | "warning" | "error";
type BackendName = "telegram" | "ios-switch" | "vibration" | "noop";

type HapticEvent =
  | { kind: "impact"; style?: ImpactStyle }
  | { kind: "notification"; type?: NotificationType }
  | { kind: "selection" };

Методы

| Метод | Описание | | ------------------- | -------------------------------------------------------------------------- | | impact(style?) | Короткий тап — кнопки, тумблеры, конец drag-жеста. | | notify(type?) | Отклик на событие — успех, предупреждение, ошибка в формах. | | selection() | Очень лёгкий тап — переключение вкладок, шаги пикера. | | trigger(event) | Отправить discriminated-событие HapticEvent. | | getBackend() | Какой бэкенд сейчас активен (noop, если ничего не подошло). | | isSupported() | true, если есть любой работающий бэкенд. | | destroy() | Освободить DOM-элементы и внутреннее состояние. Последующие вызовы станут no-op. |

Опции

createHaptic({
  backend: "telegram" | "ios-switch" | "vibration" | "noop",
  disabled: boolean,
});

Приоритет бэкендов

При первом вызове tappt берёт первый доступный из списка:

  1. telegramwindow.Telegram.WebApp.HapticFeedback. Лучшее качество внутри Telegram-клиентов.
  2. ios-switch — скрытый <input type="checkbox" switch>. Только iOS Safari 17.4+.
  3. vibrationnavigator.vibrate(pattern). Android и большинство десктопных браузеров.
  4. noop — тихий fallback (старые iOS, браузеры с ограничениями, SSR).

Передай backend: "..." в createHaptic(), чтобы выбрать конкретный бэкенд принудительно — например, пропустить Telegram даже внутри Mini App.

SSR

Все API безопасно импортировать на сервере. Определение бэкенда откладывается до первого реального вызова impact() / notify() / selection() / trigger() — можно спокойно создавать const haptic = createHaptic() на уровне модуля и переиспользовать один и тот же экземпляр на клиенте и на сервере, без дополнительных проверок.

Матрица возможностей

Не каждый бэкенд умеет всё. tappt всегда что-то вызывает, но что именно почувствует пользователь — зависит от платформы:

| Метод | Telegram Mini App | iOS Safari 17.4+ (ios-switch) | Android / Vibration API | Noop | | -------------------- | ----------------- | ------------------------------- | ---------------------------- | ---- | | impact("light") | Различимо | Один пульс (стиль игнорируется) | 8 мс вибрации | — | | impact("medium") | Различимо | Один пульс (стиль игнорируется) | 15 мс вибрации | — | | impact("heavy") | Различимо | Один пульс (стиль игнорируется) | 25 мс вибрации | — | | impact("rigid") | Различимо | Один пульс (стиль игнорируется) | 25 мс вибрации | — | | impact("soft") | Различимо | Один пульс (стиль игнорируется) | 8 мс вибрации | — | | notify("success") | Различимо | 2 пульса | паттерн [12, 40, 12] | — | | notify("warning") | Различимо | 2 пульса | паттерн [10, 40, 10] | — | | notify("error") | Различимо | 3 пульса | [10, 60, 10, 60, 10] | — | | selection() | Различимо | Один пульс | 5 мс вибрации | — |

Известные ограничения

  • iOS Safari не различает стили impact. Элемент <input type="checkbox" switch> даёт ровно один тип Taptic-импульса — публичного iOS Web API, который давал бы полный доступ к UIImpactFeedbackGenerator, не существует. impact("light") и impact("heavy") в Safari ощущаются одинаково. В Telegram Mini App на iOS они различаются: там клиент передаёт вызов в нативный слой.
  • Поведение Vibration API на Android сильно зависит от устройства. Некоторые телефоны обрезают короткую вибрацию (меньше 10 мс), другие игнорируют паттерны в режиме энергосбережения. Не закладывай смысл в тонкие различия длительности — UX должен работать, даже если любой отклик означает просто «что-то произошло».
  • notify() на iOS Safari эмулируется серией пульсов с паузой 55 мс. Вызовы, идущие подряд, ставятся в очередь и не накладываются друг на друга.
  • navigator.vibrate срабатывает только в ответ на действие пользователя в большинстве браузеров. tappt не обходит это ограничение — вызывай haptic-методы из обработчиков click/touch.

Поддержка браузеров

  • Telegram Mini Apps (iOS + Android)
  • iOS Safari 17.4+ (PWA или в браузере)
  • Chrome, Edge, Firefox, Samsung Internet (Android) — через Vibration API
  • Всё остальное — тихий no-op

Лицензия

MIT