Skip to content

mrksye/yubisac

Repository files navigation

yubisac 🫰

Touch-first intent binding. Design for the tablet — a tap, a long-press — then let the mouse, pen and keyboard cling to the same intent. One code path, touch-native, desktop along for the ride.

Pronounced "yubi-sac" — the name respells 指サック, the little cot worn on a fingertip for precise single-finger work.

import { yubisac } from 'yubisac'

yubisac(card, {
  inspect: { touch: 'long-press', mouse: 'double-click', pen: 'long-press', keyboard: 'Enter' },
}).clingTo('inspect', () => openInspector(card))

openInspector fires whether you long-pressed on a tablet, double-clicked on a laptop, or pressed Enter. The app names the intent — inspect — never the gesture.

A shift, not another gesture engine

The world is going touch-first — a tablet on every job site — but input libraries are still desktop-first: the click is the canonical action, and touch is bolted on. yubisac flips it. The intent is the anchor, and every input clings to it — static cling, the everyday electrostatic pull — so a touch long-press, a desktop double-click and an Enter key are the one inspect.

Hammer.js, interact.js, @use-gesture and tinygesture recognise gestures and hand them back as separate events; Pointer Events unify the device but stop at which device. The layer above — binding different inputs to the same intent — is the one nobody carved out. That's all yubisac is, on purpose: grow pinch/rotate/swipe and it's on Hammer's turf, out-muscled. Drag lives in its sibling goonteh (軍手, work gloves for gripping); yubisac (指サック, a finger cot for precise taps) never drags.

Recognition is a deliberately small set — click · double-click · long-press · right-click · Enter/Space — and stays small. The value is the deterministic conflict resolution underneath (single-vs-double wait, move cancels a hold, pointercancel on scroll, post-touch compatibility-mouse dedup), plus keyboard as a free accessibility layer.

Runs where a framework can't — CDN, plain HTML

Zero dependencies, framework-free core. One <script> and you're clinging intents — no bundler, no npm install (even inside a Google Apps Script HtmlService page):

<script src="https://unpkg.com/yubisac@0.9.1"></script>
<script>
  yubisac(document.getElementById('card'), {
    inspect: { touch: 'long-press', mouse: 'double-click' },
  }).clingTo('inspect', () => openInspector())
</script>

Frameworks

npm i yubisac

Thin adapters wrap the same core — install only the one you use (each an optional peer dependency); the core needs nothing.

Import Framework
yubisac / yubisac/core Framework-agnostic engine (vanilla TS + DOM)
yubisac/native Vanilla DOM sugar (cling)
yubisac/solid SolidJS — cling, <Cling>
yubisac/react React ≥ 18 — useCling, <Cling>
yubisac/vue Vue ≥ 3.2 — v-cling directive
yubisac/svelte Svelte ≥ 4 — use:cling action
yubisac/react-native React Native (experimental — its own PanResponder recogniser, touch only)

📖 EXAMPLES.md — presets, custom profiles, keyboard/accessibility, the conflict-resolution cases, and per-adapter recipes.

API (core)

yubisac(el, profile?, config?){ clingTo(intent, handler) → off(), destroy() }.

  • profile{ [intent]: { touch?, mouse?, pen?, keyboard? } }; built-in PRESETS (activate, secondary-activate, context) fill in known names.
  • gestures — click/tap, double-click/double-tap, long-press, right-click/context-menu.
  • config — two knobs only: { hold=500, tolerance=8 }.
  • handler receives { intent, pointerType, clientX, clientY, currentTarget, originalEvent }.

Status

Born on a construction-site scheduling app that had to work on a foreman's tablet (touch) and an office laptop (mouse) from one code path. Recognition stays small; the investment is the binding and the conflict resolution, not more gestures. The core is pinned by real-browser tests (Playwright / Chromium) — bun run test — covering single-vs-double timing, long-press vs move/scroll, pointercancel, compatibility-mouse dedup, keyboard, and teardown.

Contributing · License

Found a bug? Please open an issue first — see CONTRIBUTING.md. MIT © mrksye.

One intent, every finger.

About

Touch-first intent binding — bind an intent once, and touch, mouse, pen & keyboard all cling to it.

Resources

Contributing

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages