prediction-cone v0.3
📦 npm install prediction-cone

Prediction Cone
& Safe Triangle

High-performance TypeScript library for radial context menus with prediction cone pointer selection and dropdown safe triangle submenu navigation.

TypeScript Zero deps ~6 kB gzip ESM + CJS

Quick Start

Install

terminal
npm install prediction-cone
# or: pnpm add prediction-cone / bun add prediction-cone

Prediction cone radial menu

main.ts
import { createPredictionConeMenu } from 'prediction-cone';

const menu = createPredictionConeMenu({
  items: [
    { id: 'cut',   label: 'Cut',   icon: '✂️' },
    { id: 'copy',  label: 'Copy',  icon: '📋' },
    { id: 'paste', label: 'Paste', icon: '📌' },
  ],
  ringRadius: 110,
  coneHalfAngleDeg: 22.5,
  deadzone: 30,
});

// Attach to the entire page
menu.attach(document, { trigger: 'contextmenu' });

// Events
menu.on('select', ({ item }) => console.log('Selected:', item?.label));
menu.on('change', ({ item }) => console.log('Highlighted:', item?.label));

// Cleanup later
// menu.destroy();

Dropdown with safe triangle submenus

dropdown-menu.ts
import { createDropdownMenu } from 'prediction-cone';

const dropdown = createDropdownMenu({
  items: [
    {
      id: 'file', label: 'File', icon: '📁',
      children: [
        { id: 'new',    label: 'New File',   icon: '📄' },
        { id: 'open',   label: 'Open…',      icon: '📂' },
        { id: 'save',   label: 'Save',       icon: '💾' },
        { id: 'sep',    separator: true },
        { id: 'export', label: 'Export As…', icon: '📤' },
      ],
    },
    {
      id: 'edit', label: 'Edit', icon: '✏️',
      children: [
        { id: 'undo',  label: 'Undo',  icon: '↩️' },
        { id: 'redo',  label: 'Redo',  icon: '↪️' },
        { id: 'sep2',  separator: true },
        { id: 'copy',  label: 'Copy',  icon: '📋' },
        { id: 'paste', label: 'Paste', icon: '📄', disabled: false },
      ],
    },
    { id: 'help', label: 'Help', icon: '❓' },
  ],
  submenuDelay: 150,  // grace period for diagonal pointer movement
  debug: false,       // set true to render safe-triangle canvas overlay
});

dropdown.attach(document.getElementById('menu-btn')!, { trigger: 'click' });

dropdown.on('select', ({ item, parentItem }) => {
  console.log(`${parentItem?.label ?? ''} › ${item?.label}`);
});

// Change items at runtime
dropdown.setItems([{ id: 'new-item', label: 'Dynamic item', icon: '✨' }]);

Architecture & Variants

🎯
Prediction Cone

Radial menu — fastest for action selection. Uses pointer direction + velocity to predict intent.

🔺
Safe Triangle

Dropdown with nested submenus — keeps menus open while cursor travels diagonally.

⚡
Hybrid UX

Radial for power users, dropdown for broad command sets. Reuse the same item structures.

Comparison

Variant Best for Key options
Prediction cone High-speed action selection, context menus coneHalfAngleDeg, deadzone, showViz
Dropdown + safe triangle Nested labels, discoverable commands children, submenuDelay
Hybrid Power users + broad command sets Shared items structures

createPredictionConeMenu

createPredictionConeMenu(options)

function

Creates a radial prediction cone menu. Returns a ConeMenuInstance.

ConeOptions

OptionTypeDefaultDescription
itemsrequired ConeItem[] — Menu items to display
ringRadius number | (vp) ⇒ number 110 Distance from center to item centers (px)
itemSize number | (vp) ⇒ number 56 Width / height of each menu item (px)
deadzone number 30 Radius near center where no selection occurs (px)
coneHalfAngleDeg number 22.5 Half-angle of the selection cone (degrees)
hysteresisDeg number 3 Stability angle to prevent selection flickering (degrees)
startAngleDeg number -90 Starting angle for item distribution (degrees; -90 = top)
edgePadding number 16 Minimum padding from viewport edges (px)
preferSafePlacement boolean true Auto-shift menu to keep all items in viewport
container HTMLElement document.body Parent element for menu DOM nodes
theme "light" | "dark" | Record "light" Visual theme — string preset or CSS variable map
showViz boolean false Render debug canvas overlay (development only)
longPressMs number 250 Long-press hold duration for touch devices (ms)

ConeItem

interface ConeItem {
  id:        string;
  label:     string;
  disabled?: boolean;
  icon?:     string | HTMLElement | (() => HTMLElement);
}

ConeMenuInstance — methods

menu.attach(target, options?)   // bind to Element or Document
menu.detach(target?)            // unbind
menu.openAt(x, y, context?)     // open at coordinates programmatically
menu.close()                    // close the menu
menu.setItems(items)            // replace item list
menu.setOptions(partial)        // patch options at runtime
menu.isOpen()                   // → boolean
menu.getState()                 // → Readonly<ConeMenuState>
menu.destroy()                  // full cleanup

Events

menu.on('open',   e => { /* menu opened            */ })
menu.on('close',  e => { /* menu closed            */ })
menu.on('change', e => { /* highlighted item changed */ })
menu.on('select', e => { /* item confirmed on release */ })

// Every .on() returns an unsubscribe function:
const unsub = menu.on('select', handler);
unsub(); // ← removes the listener

createDropdownMenu

createDropdownMenu(options)

function

Creates a dropdown menu with built-in safe triangle submenu navigation. Supports unlimited nesting via children.

Key options

OptionTypeDefaultDescription
itemsrequired DropdownItem[] — Item tree — add children array for submenus
submenuDelay number 150 Grace period before closing submenu during diagonal cursor movement (ms)
theme "light" | "dark" "light" Visual theme
debug boolean false Render safe-triangle canvas overlay (development only)

DropdownItem

interface DropdownItem {
  id:          string;
  label?:      string;
  icon?:       string | HTMLElement;
  children?:   DropdownItem[];   // → opens a submenu
  separator?:  boolean;          // → renders a divider row
  disabled?:   boolean;
  danger?:     boolean;          // → red text variant
}

SafeTriangle

new SafeTriangle(options)

class

Low-level safe triangle geometry. Use when building a custom menu system but still want diagonal-movement tolerance that prevents premature submenu closing.

custom-menu.ts
import { SafeTriangle } from 'prediction-cone';

const st = new SafeTriangle({ delay: 150, padding: 2 });

// Call when a submenu opens — pass cursor position + submenu DOMRect
st.activate({ x: 240, y: 180 }, submenuRect);

// Check on every pointermove
if (st.isInSafeZone(pointer.x, pointer.y)) {
  // Cursor is inside the triangle → keep submenu open
}

MouseTracker

new MouseTracker(options?)

class

Tracks pointer position and velocity with RAF-throttled updates. Used internally by the cone menu; also exported for custom integrations.

import { MouseTracker } from 'prediction-cone';

const tracker = new MouseTracker();
tracker.start();

// Anywhere in your render loop:
const { x, y, vx, vy } = tracker.getState();

// Cleanup:
tracker.stop();

API Playground

Add, edit, and manage annotated code snippets. Stored in localStorage — use Export to share as JSON.

Showing 0 example(s)


SEO aliases: prediction cone · safe triangle · safety triangle · menu-aim · submenu navigation.