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

momoi-keybind

v1.0.3

Published

A VSCode-like keybinding system for TypeScript projects

Readme

momoi-keybind

This project was built with Claude Code.

A VSCode-like keybinding system for TypeScript projects.

Features

  • Standard shortcuts: Ctrl+S, Ctrl+Shift+P, etc.
  • Chord (two-step input): VSCode-style sequences like Ctrl+K Ctrl+C
  • Modifier key double-press: Ctrl Ctrl, Shift Shift, etc.
  • When clauses: Context-based control like editorFocus && !inputFocus
  • Mac/Windows support: Auto-switch with CommandOrControl+S
  • User customization: Change shortcuts via JSON config files
  • Preference API: Built-in logic for building keybinding settings UI

Installation

npm install momoi-keybind

Basic Usage

1. Initialize InputService and Register Commands

import { InputService } from 'momoi-keybind'

const input = new InputService({
  defaultKeybindings: [
    { key: 'Ctrl+S', command: 'file.save', when: 'editorFocus' },
    { key: 'Ctrl+K Ctrl+C', command: 'editor.commentLine' },
    { key: 'Ctrl Ctrl', command: 'quickOpen' },
    { key: 'Shift Shift', command: 'searchEverywhere' },
  ],
})

// Register commands
input.registerCommand('file.save', () => {
  console.log('File saved')
})

input.registerCommand('quickOpen', () => {
  console.log('Quick open')
})

// Set context
input.setContext('editorFocus', true)

// Start listening
input.start()

2. Unregister via Disposer

const dispose = input.registerCommand('file.save', handler)
dispose() // unregister

3. Event Subscription

input.on('commandExecuted', (commandId, args) => {
  console.log(`Executed: ${commandId}`, args)
})

input.on('chordWaiting', (prefix) => {
  showStatusBar(`(${prefix}) was pressed. Waiting for next key...`)
})

input.on('chordCancelled', () => {
  hideStatusBar()
})

4. Lifecycle

input.start()    // Start listening
input.stop()     // Stop listening (resumable)
input.dispose()  // Full cleanup

Key Format

| Type | Example | Description | |---|---|---| | Single key | "F1", "Escape" | Single key press | | Modifier+key | "Ctrl+S", "Ctrl+Shift+P" | Combined with + | | Chord | "Ctrl+K Ctrl+C" | Two-step input separated by space | | Modifier double-press | "Ctrl Ctrl", "Shift Shift" | Same modifier key separated by space | | Cross-platform | "CommandOrControl+S" | Cmd on macOS, Ctrl elsewhere |

Modifier Key Aliases

| Alias | Key | |---|---| | Ctrl, Control | Control | | Shift | Shift | | Alt, Option | Alt | | Meta, Cmd, Command, Win, Super | Meta |

When Clauses

Same expression syntax as VSCode. Evaluates boolean expressions against context variables.

// Simple boolean
{ "key": "Ctrl+S", "command": "file.save", "when": "editorFocus" }

// Negation
{ "key": "Ctrl+P", "command": "quickOpen", "when": "!panelVisible" }

// AND
{ "key": "Ctrl+D", "command": "item.delete", "when": "listFocus && hasSelection" }

// OR
{ "key": "Enter", "command": "confirm", "when": "dialogOpen || menuOpen" }

// Comparison
{ "key": "Ctrl+Enter", "command": "editor.run", "when": "mode === 'edit'" }

Setting context variables:

input.setContext('editorFocus', true)
input.setContext('mode', 'edit')
input.deleteContext('editorFocus')

Switching Shortcuts per View

Just change context with setContext — when clauses are re-evaluated automatically on each key press.

const input = new InputService({
  defaultKeybindings: [
    { key: 'Ctrl+Enter', command: 'editor.run', when: 'view === "editor"' },
    { key: 'Ctrl+Enter', command: 'chat.send', when: 'view === "chat"' },
  ],
})

// On navigation
function navigateTo(view: string) {
  input.setContext('view', view)
}

navigateTo('editor') // Ctrl+Enter → editor.run
navigateTo('chat')   // Ctrl+Enter → chat.send

User Keybinding Customization (JSON Config)

Config File Format (keybindings.json)

VSCode-compatible JSONC format. Comments are supported.

[
  // Override a key
  { "key": "Ctrl+Shift+S", "command": "file.save" },

  // Add a new binding
  { "key": "Ctrl+N", "command": "file.new" },

  // Remove a default binding (prefix command with "-")
  { "key": "", "command": "-edit.undo" }
]

Loading and Applying

import {
  InputService,
  loadKeybindingsFromFile,    // Node.js / Electron
  loadKeybindingsFromStorage, // Browser localStorage
  loadKeybindingsFromURL,     // Browser fetch
  parseKeybindingsJSON,       // From JSON string
} from 'momoi-keybind'

// --- Node.js / Electron ---
const result = await loadKeybindingsFromFile('./keybindings.json')
if (result.errors.length > 0) {
  console.warn('Config errors:', result.errors)
}

const input = new InputService({
  defaultKeybindings: myDefaults,
  userKeybindings: result.entries,
})

// --- Browser localStorage ---
const result = loadKeybindingsFromStorage('app-keybindings')
const input = new InputService({
  defaultKeybindings: myDefaults,
  userKeybindings: result.entries,
})

Saving

import { saveKeybindingsToFile, saveKeybindingsToStorage } from 'momoi-keybind'

// Node.js
await saveKeybindingsToFile('./keybindings.json', entries)

// Browser
saveKeybindingsToStorage('app-keybindings', entries)

Preference API (Building Settings UI)

KeybindingPreference is a headless logic layer for building keybinding settings UI. Framework-agnostic — works with React, Vue, Svelte, or any other framework.

Basic Usage

const pref = input.createPreference()

// Get all bindings (with source, isOverridden, isRemoved info)
const all = pref.getAll()

// Search by command
const saves = pref.getByCommand('file.save')

// Search by key (partial match)
const ctrlK = pref.getByKey('Ctrl+K', true)

Changing Keys

pref.changeKey('file.save', 'Ctrl+Shift+S', 'Ctrl+S')
// → Removes default Ctrl+S and re-registers with Ctrl+Shift+S
// → when clauses and args are automatically preserved

Shortcut Recording ("Press a new key" UI)

// Start recording
const { promise, dispose } = pref.recordShortcut()

// Show "Press a key..." in UI

const shortcut = await promise // Waits until user presses a key
// shortcut = "Ctrl+Shift+N" etc.

if (shortcut) {
  // Conflict check
  const conflict = pref.checkConflict(shortcut, 'file.save')
  if (conflict && !conflict.disjointWhen) {
    // Warn: "Ctrl+Shift+N is already assigned to file.new"
  }
  pref.changeKey('file.save', shortcut)
}

// On cancel button click
dispose() // → promise resolves with null

Conflict Detection

// Detect all conflicts
const conflicts = pref.detectAllConflicts()
// → [{ key: "Ctrl+S", entries: [...], disjointWhen: false }]

// Check conflict for a specific key (excluding self)
const conflict = pref.checkConflict('Ctrl+S', 'my.command')
if (conflict && !conflict.disjointWhen) {
  // Actually conflicting
}
// disjointWhen === true means when clauses are mutually exclusive, so no real conflict
// e.g., when="editorFocus" vs when="!editorFocus"

Add / Remove / Reset Bindings

// Add
pref.addBinding({ key: 'Ctrl+N', command: 'file.new' })

// Remove (adds "-command" entry if it's a default binding)
pref.removeBinding('edit.undo')

// Reset a specific command to default
pref.resetToDefault('file.save')

// Clear all user settings
pref.resetAll()

Saving User Settings

// Get as JSON string
const json = pref.toJSON()

// Save to file (Node.js)
await saveKeybindingsToFile('./keybindings.json', pref.getUserBindings())

// Save to localStorage (Browser)
saveKeybindingsToStorage('app-keybindings', pref.getUserBindings())

Change Notification

pref.onChange((changes) => {
  // changes: KeybindingChange[]
  // type: 'add' | 'modify' | 'remove' | 'reset'
  refreshUI()
})

Settings UI Implementation Example

const pref = input.createPreference()

// 1. Render table
function renderTable() {
  const bindings = pref.getAll()
  for (const b of bindings) {
    renderRow(b.key, b.command, b.when, b.source, b.isOverridden)
  }
}

// 2. Change key button
async function onChangeKeyClicked(command: string) {
  const { promise, dispose } = pref.recordShortcut()
  showDialog('Press a new key...')

  const shortcut = await promise
  if (!shortcut) return

  const conflict = pref.checkConflict(shortcut, command)
  if (conflict && !conflict.disjointWhen) {
    if (!confirm(`${shortcut} is already assigned to ${conflict.entries[0].command}. Override?`)) {
      return
    }
  }

  pref.changeKey(command, shortcut)
  renderTable()
}

// 3. Save
async function onSave() {
  await saveKeybindingsToFile('./keybindings.json', pref.getUserBindings())
}

// 4. Auto-save (browser)
pref.onChange(() => {
  saveKeybindingsToStorage('app-keybindings', pref.getUserBindings())
})

InputServiceOptions

interface InputServiceOptions {
  /** Event listener target (default: document) */
  target?: EventTarget
  /** Listen in capture phase (default: false) */
  capture?: boolean
  /** Modifier double-press timeout in ms (default: 300) */
  modifierDoublePressTimeout?: number
  /** Default keybindings */
  defaultKeybindings?: KeybindingEntry[]
  /** User keybindings */
  userKeybindings?: KeybindingEntry[]
}

API Reference

Classes

| Class | Description | |---|---| | InputService | Main facade. Registers keybindings, listens for input, executes commands | | KeybindingPreference | Headless logic for settings UI. Created via input.createPreference() | | KeybindingRegistry | Merges default/user settings. Not typically used directly | | ModifierDetector | Detects modifier key double-press. Not typically used directly |

Loader Functions

| Function | Environment | Description | |---|---|---| | parseKeybindingsJSON(json) | Universal | Parse JSONC string and validate | | validateKeybindings(data) | Universal | Validate parsed array | | loadKeybindingsFromFile(path) | Node.js | Load from file (returns empty on ENOENT) | | saveKeybindingsToFile(path, entries) | Node.js | Save to file | | loadKeybindingsFromURL(url) | Browser | Load via fetch | | loadKeybindingsFromStorage(key) | Browser | Load from localStorage | | saveKeybindingsToStorage(key, entries) | Browser | Save to localStorage |

Utilities

| Function | Description | |---|---| | createShortcutRecorder(options?) | Promise wrapper for ShoSho's recording | | detectConflicts(bindings) | Detect all keybinding conflicts | | checkKeyConflict(key, bindings, exclude?) | Check conflict for a specific key | | evaluateWhen(expr, context) | Synchronously evaluate a when clause | | isModifierOnlyShortcut(key) | Check if shortcut is modifier-only |

Type Definitions

All major types are importable from momoi-keybind.

import type {
  KeybindingEntry,        // { key, command, when?, args? }
  ResolvedKeybinding,     // KeybindingEntry + source
  KeybindingView,         // For preference display (isOverridden, isRemoved)
  KeyConflict,            // { key, entries, disjointWhen }
  KeybindingChange,       // Change diff (add | modify | remove | reset)
  LoadResult,             // { entries, errors }
  LoadError,              // { index, message, raw }
  ModifierKey,            // 'Control' | 'Shift' | 'Alt' | 'Meta'
  InputServiceOptions,
  InputServiceEvents,
  CommandHandler,
  ContextValues,
  RecordShortcutOptions,
} from 'momoi-keybind'

License

MIT