Prediction Cone
& Safe Triangle
High-performance TypeScript library for radial context menus with prediction cone pointer selection and dropdown safe triangle submenu navigation.
Quick Start
Install
npm install prediction-cone
# or: pnpm add prediction-cone / bun add prediction-cone
Prediction cone radial menu
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,
});
menu.on('select', ({ item }) => console.log('Selected:', item.id));
menu.attach(document, { trigger: 'contextmenu' });
Dropdown with safe triangle submenus
import { createDropdownMenu } from 'prediction-cone';
const dropdown = createDropdownMenu({
items: [
{
id: 'file',
label: 'File',
children: [
{ id: 'new', label: 'New' },
{ id: 'open', label: 'Open' },
],
},
],
submenuDelay: 150,
});
dropdown.attach(document.getElementById('btn')!, { trigger: 'click' });
Architecture & Variants
Radial menu — fastest for action selection. Uses pointer direction + velocity to predict intent.
Dropdown with nested submenus — keeps menus open while cursor travels diagonally.
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)
functionCreates a radial prediction cone menu. Returns a ConeMenuInstance.
ConeOptions
| Option | Type | Default | Description |
|---|---|---|---|
| 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)
functionCreates a dropdown menu with built-in safe triangle submenu navigation. Supports unlimited nesting via children.
Key options
| Option | Type | Default | Description |
|---|---|---|---|
| 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 |
SafeTriangle
new SafeTriangle(options)
classLow-level safe triangle geometry. Use when building a custom menu system but still want diagonal-movement tolerance that prevents premature submenu closing.
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?)
classTracks 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.
No examples found. Add one above ↑
SEO aliases: prediction cone · safe triangle · safety triangle · menu-aim · submenu navigation.