{"_id":"@affino/menu-react","_rev":"8-ed2e6033c4b2a9d762d79cb128302af9","name":"@affino/menu-react","dist-tags":{"alpha":"1.0.0-alpha.5","latest":"1.0.0"},"versions":{"1.0.0-alpha.0":{"name":"@affino/menu-react","version":"1.0.0-alpha.0","keywords":["react","menu","dropdown","context-menu","headless","ui","component","accessibility","aria","keyboard","submenu","design-system","radix","headless-ui"],"author":{"name":"Anton Pavlov","email":"pavlov@myyahho.com"},"license":"MIT","_id":"@affino/menu-react@1.0.0-alpha.0","maintainers":[{"name":"affino","email":"anton.pavlov.personal@gmail.com"}],"homepage":"https://ui.unitlab.io","bugs":{"url":"https://github.com/affinio/affinio/issues"},"dist":{"shasum":"ca5a06c1c4ef718b9dc02196f48d2c4ac240d9c9","tarball":"https://registry.npmjs.org/@affino/menu-react/-/menu-react-1.0.0-alpha.0.tgz","fileCount":31,"integrity":"sha512-bQQhIhVFIejnXfr5rtGGxcD4HldjIB6HIWVMTiEdhReXX2SVZ1Itj8nzXi8/b1+eZuRtldxl+Mm7uKmnouPABQ==","signatures":[{"sig":"MEQCIFDrvOczYFfSR6tXAyiGbp6HNeKVonjibzcKzJ0uXkrIAiBS85MTWijMglbr6YfVsUjPA7AQ13JeqZBnxs2ew17Bng==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":53736},"type":"module","_from":"file:affino-menu-react-1.0.0-alpha.0.tgz","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./styles.css":"./dist/styles.css"},"scripts":{"test":"vitest run","build":"pnpm run clean && vite build && pnpm run build:types","clean":"rm -rf dist","build:types":"tsc -p tsconfig.build.json"},"_npmUser":{"name":"affino","email":"anton.pavlov.personal@gmail.com"},"_resolved":"/tmp/2146b180f156c5e153fdc7ae70646bcf/affino-menu-react-1.0.0-alpha.0.tgz","_integrity":"sha512-bQQhIhVFIejnXfr5rtGGxcD4HldjIB6HIWVMTiEdhReXX2SVZ1Itj8nzXi8/b1+eZuRtldxl+Mm7uKmnouPABQ==","repository":{"url":"git+https://github.com/affinio/affinio.git","type":"git"},"_npmVersion":"10.8.2","description":"Headless React menu components powered by @affino/menu-core","directories":{},"_nodeVersion":"20.19.6","_hasShrinkwrap":false,"devDependencies":{"vite":"^7.2.7","jsdom":"^27.3.0","react":"^18.3.1","vitest":"^4.0.15","react-dom":"^18.3.1","typescript":"^5.9.3","@types/react":"^18.3.12","@types/react-dom":"^18.3.2","@affino/menu-core":"^1.0.0-alpha.5","@vitest/coverage-v8":"^4.0.15","@testing-library/dom":"^10.0.0","@vitejs/plugin-react":"^4.3.4","@testing-library/react":"^16.0.0","@testing-library/jest-dom":"^6.9.1","@testing-library/user-event":"^14.6.1"},"peerDependencies":{"react":"^18.2.0","react-dom":"^18.2.0","@affino/menu-core":"^1.0.0-alpha.5"},"_npmOperationalInternal":{"tmp":"tmp/menu-react_1.0.0-alpha.0_1765919781568_0.18850673707563792","host":"s3://npm-registry-packages-npm-production"}},"1.0.0-alpha.2":{"name":"@affino/menu-react","version":"1.0.0-alpha.2","keywords":["react","menu","dropdown","context-menu","headless","ui","component","accessibility","aria","keyboard","submenu","design-system","radix","headless-ui"],"author":{"name":"Anton Pavlov","email":"pavlov@myyahho.com"},"license":"MIT","_id":"@affino/menu-react@1.0.0-alpha.2","maintainers":[{"name":"affino","email":"anton.pavlov.personal@gmail.com"}],"homepage":"https://ui.unitlab.io","bugs":{"url":"https://github.com/affinio/affinio/issues"},"dist":{"shasum":"a93479bbd0d83775e7a69347ed8859c96ae56431","tarball":"https://registry.npmjs.org/@affino/menu-react/-/menu-react-1.0.0-alpha.2.tgz","fileCount":31,"integrity":"sha512-FctqoYq8C2Mj2Jsg4kwP/bEHlkiE69824WM3ESXg3UmFEEGXUBZ1P9woroBFkaTvXJcA9iy6N2SrqSUjY/jFow==","signatures":[{"sig":"MEUCIHHYGqGZFmsnwkcxDF+YPvWMnXAja3bflU2W1WjZMEAuAiEAmZ7dRcINVVaXzXs9uuX1PdF8sSuDuZ/KRjcmV6h71ek=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":53762},"type":"module","_from":"file:affino-menu-react-1.0.0-alpha.2.tgz","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./styles.css":"./dist/styles.css"},"scripts":{"test":"vitest run","build":"pnpm run clean && vite build && pnpm run build:types","clean":"rm -rf dist","build:types":"tsc -p tsconfig.build.json"},"_npmUser":{"name":"affino","email":"anton.pavlov.personal@gmail.com"},"_resolved":"/tmp/bda89234a8e0873af7077236be35ecb2/affino-menu-react-1.0.0-alpha.2.tgz","_integrity":"sha512-FctqoYq8C2Mj2Jsg4kwP/bEHlkiE69824WM3ESXg3UmFEEGXUBZ1P9woroBFkaTvXJcA9iy6N2SrqSUjY/jFow==","repository":{"url":"git+https://github.com/affinio/affinio.git#main","type":"git"},"_npmVersion":"10.8.2","description":"Headless React menu components powered by @affino/menu-core","directories":{},"_nodeVersion":"20.19.6","_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"vite":"^7.2.7","jsdom":"^27.3.0","react":"^18.3.1","vitest":"^4.0.15","react-dom":"^18.3.1","typescript":"^5.9.3","@types/react":"^18.3.12","@types/react-dom":"^18.3.2","@affino/menu-core":"^1.0.0-alpha.7","@vitest/coverage-v8":"^4.0.15","@testing-library/dom":"^10.0.0","@vitejs/plugin-react":"^4.3.4","@testing-library/react":"^16.0.0","@testing-library/jest-dom":"^6.9.1","@testing-library/user-event":"^14.6.1"},"peerDependencies":{"react":"^18.2.0","react-dom":"^18.2.0","@affino/menu-core":"^1.0.0-alpha.7"},"_npmOperationalInternal":{"tmp":"tmp/menu-react_1.0.0-alpha.2_1765925308280_0.7918110333787782","host":"s3://npm-registry-packages-npm-production"}},"1.0.0-alpha.3":{"name":"@affino/menu-react","version":"1.0.0-alpha.3","keywords":["react","menu","dropdown","context-menu","headless","ui","component","accessibility","aria","keyboard","submenu","design-system","radix","headless-ui"],"author":{"name":"Anton Pavlov","email":"a.pavlov@affino.dev"},"license":"MIT","_id":"@affino/menu-react@1.0.0-alpha.3","maintainers":[{"name":"affino","email":"anton.pavlov.personal@gmail.com"}],"homepage":"https://affino.dev","bugs":{"url":"https://github.com/affinio/affinio/issues"},"dist":{"shasum":"570320a49a979c9e7e11dcc89c636fc2c8ff5764","tarball":"https://registry.npmjs.org/@affino/menu-react/-/menu-react-1.0.0-alpha.3.tgz","fileCount":31,"integrity":"sha512-zK2Y5qYNy26h3DrvNiKNVurwr4evrJqD746wEhEwp9Wl7z5Wh9Ftg7d0xV/h8M160TtD2/f9fiUsHqy/Kn+NBQ==","signatures":[{"sig":"MEUCIDIqfCexS9MluqlXgtSdDtVEzx8Uxdnn2hdO9dHLn5yOAiEAhG4xOWOYP752//S7DSA7R6r7K0EJy92iacyQkmhiyhw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":53741},"type":"module","_from":"file:affino-menu-react-1.0.0-alpha.3.tgz","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./styles.css":"./dist/styles.css"},"scripts":{"test":"vitest run","build":"pnpm run clean && vite build && pnpm run build:types","clean":"rm -rf dist","build:types":"tsc -p tsconfig.build.json"},"_npmUser":{"name":"affino","email":"anton.pavlov.personal@gmail.com"},"_resolved":"/tmp/38e0ff1712f4e048a891028b6c820302/affino-menu-react-1.0.0-alpha.3.tgz","_integrity":"sha512-zK2Y5qYNy26h3DrvNiKNVurwr4evrJqD746wEhEwp9Wl7z5Wh9Ftg7d0xV/h8M160TtD2/f9fiUsHqy/Kn+NBQ==","repository":{"url":"git+https://github.com/affinio/affinio.git#main","type":"git"},"_npmVersion":"10.8.2","description":"Headless React menu components powered by @affino/menu-core","directories":{},"_nodeVersion":"20.19.6","_hasShrinkwrap":false,"devDependencies":{"vite":"^7.2.7","jsdom":"^27.3.0","react":"^18.3.1","vitest":"^4.0.15","react-dom":"^18.3.1","typescript":"^5.9.3","@types/react":"^18.3.12","@types/react-dom":"^18.3.2","@affino/menu-core":"^1.0.0-alpha.8","@vitest/coverage-v8":"^4.0.15","@testing-library/dom":"^10.0.0","@vitejs/plugin-react":"^4.3.4","@testing-library/react":"^16.0.0","@testing-library/jest-dom":"^6.9.1","@testing-library/user-event":"^14.6.1"},"peerDependencies":{"react":"^18.2.0","react-dom":"^18.2.0","@affino/menu-core":"^1.0.0-alpha.8"},"_npmOperationalInternal":{"tmp":"tmp/menu-react_1.0.0-alpha.3_1765984023512_0.4136454491966035","host":"s3://npm-registry-packages-npm-production"}},"1.0.0-alpha.4":{"name":"@affino/menu-react","version":"1.0.0-alpha.4","keywords":["react","menu","dropdown","context-menu","headless","ui","component","accessibility","aria","keyboard","submenu","design-system","radix","headless-ui"],"author":{"name":"Anton Pavlov","email":"a.pavlov@affino.dev"},"license":"MIT","_id":"@affino/menu-react@1.0.0-alpha.4","maintainers":[{"name":"affino","email":"anton.pavlov.personal@gmail.com"}],"homepage":"https://affino.dev","bugs":{"url":"https://github.com/affinio/affinio/issues"},"dist":{"shasum":"e60281e10891e57c618e4b5f4114890e68c51a53","tarball":"https://registry.npmjs.org/@affino/menu-react/-/menu-react-1.0.0-alpha.4.tgz","fileCount":31,"integrity":"sha512-H8wVQy4y+OYw0EISqSrL6L1ZslCVCUuK6D1HW3ja0I3MxvE+PQ/mleLDJbmxxVMf0FF9Y6su3ik+PTqhptojug==","signatures":[{"sig":"MEYCIQC9eFnGkifpkhAml5PhCW/yT9WXX2Y32doAMyTroJwK4wIhAKuwcoLT10bVNSA1RLM/KORmlQ8Af/xtE8Q9JXQjg7+D","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":53845},"type":"module","_from":"file:affino-menu-react-1.0.0-alpha.4.tgz","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./styles.css":"./dist/styles.css"},"scripts":{"test":"vitest run","build":"pnpm run clean && vite build && pnpm run build:types","clean":"rm -rf dist","build:types":"tsc -p tsconfig.build.json"},"_npmUser":{"name":"affino","email":"anton.pavlov.personal@gmail.com"},"_resolved":"/tmp/f06ad5ebed091777f973ba11e836ab49/affino-menu-react-1.0.0-alpha.4.tgz","_integrity":"sha512-H8wVQy4y+OYw0EISqSrL6L1ZslCVCUuK6D1HW3ja0I3MxvE+PQ/mleLDJbmxxVMf0FF9Y6su3ik+PTqhptojug==","repository":{"url":"git+https://github.com/affinio/affinio.git#main","type":"git"},"_npmVersion":"10.8.2","description":"Headless React menu components powered by @affino/menu-core","directories":{},"_nodeVersion":"20.19.6","_hasShrinkwrap":false,"devDependencies":{"vite":"^7.2.7","jsdom":"^27.3.0","react":"^18.3.1","vitest":"^4.0.15","react-dom":"^18.3.1","typescript":"^5.9.3","@types/react":"^18.3.12","@types/react-dom":"^18.3.2","@affino/menu-core":"^1.0.0-alpha.8","@vitest/coverage-v8":"^4.0.15","@testing-library/dom":"^10.0.0","@vitejs/plugin-react":"^4.3.4","@testing-library/react":"^16.0.0","@testing-library/jest-dom":"^6.9.1","@testing-library/user-event":"^14.6.1"},"peerDependencies":{"react":"^18.2.0","react-dom":"^18.2.0","@affino/menu-core":"^1.0.0-alpha.8"},"_npmOperationalInternal":{"tmp":"tmp/menu-react_1.0.0-alpha.4_1765990354207_0.1190942767283798","host":"s3://npm-registry-packages-npm-production"}},"1.0.0-alpha.5":{"name":"@affino/menu-react","version":"1.0.0-alpha.5","keywords":["react","menu","dropdown","context-menu","headless","ui","component","accessibility","aria","keyboard","submenu","design-system","radix","headless-ui"],"author":{"name":"Anton Pavlov","email":"a.pavlov@affino.dev"},"license":"MIT","_id":"@affino/menu-react@1.0.0-alpha.5","maintainers":[{"name":"affino","email":"anton.pavlov.personal@gmail.com"}],"homepage":"https://affino.dev","bugs":{"url":"https://github.com/affinio/affinio/issues"},"dist":{"shasum":"66de48a3494b5aa444099bd4c56cea5f2f084817","tarball":"https://registry.npmjs.org/@affino/menu-react/-/menu-react-1.0.0-alpha.5.tgz","fileCount":31,"integrity":"sha512-IxpjByfGDfIb4nkq5jnnotvJn75uxcEQWQ7xJBfP2izqpdxoQPIUyMc3eV/UreVxfBEdNyHfZGlhiW+qF7C4FA==","signatures":[{"sig":"MEUCIEmIhwfjE7eyuNDNrOJ1ofOjZt0BWV+BPEYuyoTWUqorAiEAyetZLHrHlOxnvbHBBAGgHAvpmu7zeSDQhjfvAi3M8YQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":53845},"type":"module","_from":"file:affino-menu-react-1.0.0-alpha.5.tgz","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./styles.css":"./dist/styles.css"},"scripts":{"test":"vitest run","build":"pnpm run clean && vite build && pnpm run build:types","clean":"rm -rf dist","build:types":"tsc -p tsconfig.build.json"},"_npmUser":{"name":"affino","email":"anton.pavlov.personal@gmail.com"},"_resolved":"/tmp/a5c2b88fbba5033e2d7910ad2648f3f1/affino-menu-react-1.0.0-alpha.5.tgz","_integrity":"sha512-IxpjByfGDfIb4nkq5jnnotvJn75uxcEQWQ7xJBfP2izqpdxoQPIUyMc3eV/UreVxfBEdNyHfZGlhiW+qF7C4FA==","repository":{"url":"git+https://github.com/affinio/affinio.git#main","type":"git"},"_npmVersion":"10.8.2","description":"Headless React menu components powered by @affino/menu-core","directories":{},"_nodeVersion":"20.19.6","_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"vite":"^7.2.7","jsdom":"^27.3.0","react":"^18.3.1","vitest":"^4.0.15","react-dom":"^18.3.1","typescript":"^5.9.3","@types/react":"^18.3.12","@types/react-dom":"^18.3.2","@affino/menu-core":"^1.0.0-alpha.9","@vitest/coverage-v8":"^4.0.15","@testing-library/dom":"^10.0.0","@vitejs/plugin-react":"^4.3.4","@testing-library/react":"^16.0.0","@testing-library/jest-dom":"^6.9.1","@testing-library/user-event":"^14.6.1"},"peerDependencies":{"react":"^18.2.0","react-dom":"^18.2.0","@affino/menu-core":"^1.0.0-alpha.9"},"_npmOperationalInternal":{"tmp":"tmp/menu-react_1.0.0-alpha.5_1768945471913_0.44397959405830534","host":"s3://npm-registry-packages-npm-production"}},"1.0.0":{"name":"@affino/menu-react","version":"1.0.0","author":{"name":"Anton Pavlov","email":"a.pavlov@affino.dev"},"type":"module","description":"Headless React menu components powered by @affino/menu-core","exports":{".":{"import":"./dist/index.js","types":"./dist/index.d.ts"},"./styles.css":"./dist/styles.css"},"peerDependencies":{"@affino/menu-core":"^1.0.0","react":"^18.2.0","react-dom":"^18.2.0"},"devDependencies":{"@testing-library/dom":"^10.0.0","@testing-library/jest-dom":"^6.9.1","@testing-library/react":"^16.0.0","@testing-library/user-event":"^14.6.1","@types/react":"^18.3.12","@types/react-dom":"^18.3.2","@vitejs/plugin-react":"^4.3.4","@vitest/coverage-v8":"^4.0.15","jsdom":"^27.3.0","react":"^18.3.1","react-dom":"^18.3.1","typescript":"^5.9.3","vite":"^7.2.7","vitest":"^4.0.15","@affino/menu-core":"^1.0.0"},"repository":{"type":"git","url":"git+https://github.com/affinio/affinio.git#main"},"homepage":"https://affino.dev","keywords":["react","menu","dropdown","context-menu","headless","ui","component","accessibility","aria","keyboard","submenu","design-system","radix","headless-ui"],"license":"MIT","scripts":{"clean":"rm -rf dist","build":"pnpm run clean && vite build && pnpm run build:types","build:types":"tsc -p tsconfig.build.json","test":"vitest run"},"_id":"@affino/menu-react@1.0.0","bugs":{"url":"https://github.com/affinio/affinio/issues"},"_integrity":"sha512-doGkoJChO4EJlJKNMh7P48hYjghgiljMESUakmTwaGGzsludwUyP0QYv5AaPp6ykvWt8OSj6bkDlk+xEqWGIgA==","_resolved":"/tmp/ce235c5bc95ec684d708c4a5a6fa8856/affino-menu-react-1.0.0.tgz","_from":"file:affino-menu-react-1.0.0.tgz","_nodeVersion":"20.19.6","_npmVersion":"10.8.2","dist":{"integrity":"sha512-doGkoJChO4EJlJKNMh7P48hYjghgiljMESUakmTwaGGzsludwUyP0QYv5AaPp6ykvWt8OSj6bkDlk+xEqWGIgA==","shasum":"26223b23e6642f1e8c1133a62180748bd19f5fdf","tarball":"https://registry.npmjs.org/@affino/menu-react/-/menu-react-1.0.0.tgz","fileCount":5,"unpackedSize":45161,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIEa/f7XyEuclPxwenXJwdGMi1EQQdcBeg9c1pfSjCBbuAiBqMTIgvsAsI+ZpTHC0jAg7oinpZzaTxLb2zYzEjpEEBQ=="}]},"_npmUser":{"name":"affino","email":"anton.pavlov.personal@gmail.com"},"directories":{},"maintainers":[{"name":"affino","email":"anton.pavlov.personal@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/menu-react_1.0.0_1769943339872_0.7617499160832892"},"_hasShrinkwrap":false}},"time":{"created":"2025-12-16T21:16:21.431Z","modified":"2026-02-01T10:55:40.135Z","1.0.0-alpha.0":"2025-12-16T21:16:21.701Z","1.0.0-alpha.2":"2025-12-16T22:48:28.447Z","1.0.0-alpha.3":"2025-12-17T15:07:03.656Z","1.0.0-alpha.4":"2025-12-17T16:52:34.343Z","1.0.0-alpha.5":"2026-01-20T21:44:32.195Z","1.0.0":"2026-02-01T10:55:40.030Z"},"bugs":{"url":"https://github.com/affinio/affinio/issues"},"author":{"name":"Anton Pavlov","email":"a.pavlov@affino.dev"},"license":"MIT","homepage":"https://affino.dev","keywords":["react","menu","dropdown","context-menu","headless","ui","component","accessibility","aria","keyboard","submenu","design-system","radix","headless-ui"],"repository":{"type":"git","url":"git+https://github.com/affinio/affinio.git#main"},"description":"Headless React menu components powered by @affino/menu-core","maintainers":[{"name":"affino","email":"anton.pavlov.personal@gmail.com"}],"readme":"# @affino/menu-react\n\n> React's most precise headless menu system — deterministic controllers, diagonal pointer intent, and zero styling opinions.\n\n```tsx\nimport { UiMenu, UiMenuTrigger, UiMenuContent, UiMenuItem } from \"@affino/menu-react\"\n\nconst actions = [\"Edit\", \"Duplicate\", \"Archive\"]\n\nexport function ActionsMenu() {\n  return (\n    <UiMenu>\n      <UiMenuTrigger>Actions</UiMenuTrigger>\n      <UiMenuContent>\n        {actions.map((action) => (\n          <UiMenuItem key={action} onSelect={() => console.log(action)}>\n            {action}\n          </UiMenuItem>\n        ))}\n      </UiMenuContent>\n    </UiMenu>\n  )\n}\n```\n\n```bash\nnpm install @affino/menu-react\n```\n\n## Core Features\n\n- Headless React components powered by `@affino/menu-core`\n- Smart mouse prediction keeps submenus open during diagonal travel\n- Unlimited submenu depth with shared tree state and safe focus handoffs\n- Context-menu + click menu support with a single trigger surface\n- Auto positioning and viewport collision handling without extra deps\n- Snapshot-driven controller subscriptions for zero wasted renders\n- `asChild` pattern so you can slot in any DOM structure or design system primitive\n- Programmatic controller API for imperative flows (`open`, `close`, `highlight`, `select`)\n- CSS variables for seamless theming (light/dark/brand palettes)\n- First-class TypeScript types for every prop, event payload, and controller method\n\nDocs → [../../docs/index.md](../../docs/index.md)\n\n## Getting Started\n\n1. `npm install @affino/menu-react`\n2. Import the base styles once in your app entry so the design tokens are available everywhere:\n\n```ts\nimport \"@affino/menu-react/styles.css\"\n```\n\n3. Compose `<UiMenu>`, `<UiMenuTrigger>`, `<UiMenuContent>`, and `<UiMenuItem>` to build dropdowns, context menus, or nested navigation.\n4. Nest `<UiSubMenu>` components for infinite submenu depth.\n5. Use the supplied hooks (`useMenu`, `useMenuShortcuts`) to drive programmatic flows.\n\n## Controller Surface\n\n`useMenuController` now builds on top of `createMenuTree`, so the hook exposes the same pointer + geometry adapters the core uses internally. Opt into them whenever you need custom positioning or analytics—no `instanceof SubmenuCore` casts required.\n\n```tsx\nconst controller = useMenuController({ kind: \"root\", options, callbacks })\n\nuseEffect(() => {\n  if (!controller.state.open) return\n  console.log(\"Active item\", controller.state.activeItemId)\n}, [controller.state.open, controller.state.activeItemId])\n\ncontroller.recordPointer?.({ x: event.clientX, y: event.clientY })\ncontroller.setTriggerRect?.(controller.triggerRef.current?.getBoundingClientRect() ?? null)\ncontroller.setPanelRect?.(controller.panelRef.current?.getBoundingClientRect() ?? null)\ncontroller.setAnchor(controller.triggerRef.current?.getBoundingClientRect() ?? null)\n```\n\n- `recordPointer` feeds diagonal intent detection, enabling alternate input sources (stylus, remote desktop, Playwright pointer hooks).\n- `setTriggerRect` / `setPanelRect` keep submenu geometry fresh after layout transitions without poking private APIs.\n- `setAnchor` supports context menus, palette-style overlays, and anchored inspectors with zero custom timers.\n\n## Headless usage with createMenuTree\n\nRolling your own renderer? Instantiate the helper directly to get the same branch objects the React hook relies on.\n\n```ts\nimport { createMenuTree } from \"@affino/menu-core\"\n\nconst tree = createMenuTree({ options: { openDelay: 75, closeDelay: 120 } })\ntree.root.registerItem(\"file\")\n\nconst submenu = tree.createSubmenu({\n  parent: tree.root,\n  parentItemId: \"file\",\n})\n\nconst syncGeometry = () => {\n  submenu.geometry?.sync({\n    trigger: document.querySelector(\"[data-file]\")?.getBoundingClientRect() ?? null,\n    panel: document.querySelector(\"[data-file-panel]\")?.getBoundingClientRect() ?? null,\n  })\n}\n\nwindow.addEventListener(\"pointermove\", (event) => {\n  submenu.pointer?.record({ x: event.clientX, y: event.clientY })\n})\n\nsyncGeometry()\n\n// Clean up every branch when you unmount\ntree.destroy()\n```\n\nThis keeps third-party design systems, test harnesses, and custom renderers aligned with the official adapters.\n\n## Live Examples\n\n**Try it yourself in under 30 seconds:**\n\n- 🚀 **[Demos →](https://affino.dev)**\n\n## FAQ\n\n- **Does it work with Next.js / SSR?** Yes — everything renders on the server, hydrates on the client, and pointer-only features no-op until `window` exists.\n- **Can I open at pointer coordinates?** Call `controller.setAnchor({ x, y, width: 0, height: 0 })` before `controller.open(\"pointer\")` or rely on the built-in context menu trigger.\n- **How do I keep my own DOM?** Pass `asChild` to any trigger or item component and Affino will clone + merge props while preserving refs/events.\n- **What about 1000+ items?** Pair `<UiMenuContent>` with your favorite virtualization library; controllers stay snapshot-driven and never depend on render count.\n\n## Browser Support\n\n- Evergreen Chromium, Firefox, Safari (ES2020+)\n- React 18+\n- TypeScript 5+\n\n## License\n\nMIT © affino OSS","readmeFilename":"README.md"}