Vue Menu Aim
Vue Menu Aim is a Vue.js plugin designed to implement the 'menu-aim' functionality for improving the user experience with complex nested menus. This pattern, often popularized by Amazon, intelligently detects a user's intent to move their mouse from a parent menu item to its corresponding submenu, preventing accidental deactivation or closure of the submenu when the mouse briefly passes over other menu items. It calculates a 'forgiving triangle' area, allowing users to move diagonally without triggering hover-out events on intermediate elements. The package is currently at version 1.2.0 and has not seen updates since May 2019, making it primarily compatible with Vue 2 applications. It is particularly useful for projects requiring sophisticated dropdown or fly-out menus where standard hover delays are insufficient to provide a smooth interaction. It does not follow a regular release cadence and is no longer actively maintained.
Common errors
-
TypeError: Vue.use is not a function
cause Attempting to use `Vue.use()` with a Vue 3 application. In Vue 3, plugins are installed directly on the app instance (`app.use(plugin)`), and the global `Vue` object no longer has a `use` method.fixEnsure you are using `vue-menu-aim` only with Vue 2 projects. If migrating to Vue 3, this library is incompatible and needs to be replaced. For Vue 2, ensure `Vue` is globally available or correctly imported if using a build system: `import Vue from 'vue'; Vue.use(vueMenuAim);` -
Submenu closes immediately or does not appear reliably when moving mouse to it.
cause The `top`, `left`, `width`, or `height` options passed to `vueMenuAim` are incorrect, causing the 'forgiving triangle' area calculation to be misaligned with the actual menu and submenu positions.fixCarefully inspect the `offsetTop`, `offsetLeft`, `offsetWidth`, and `offsetHeight` properties of your main menu element in the DOM at runtime. Ensure these values are accurately passed to the `vueMenuAim` configuration. Adjust `submenuDirection` if your submenu opens in a direction other than the default 'right'. -
Property '$vueMenuAim' does not exist on type 'Vue'.
cause TypeScript error indicating that the `vueMenuAim` method injected by the plugin is not recognized on the Vue instance type, likely due to missing or incorrect type declarations.fixThis library does not ship with TypeScript definitions. You would need to create a declaration file (e.g., `vue-menu-aim.d.ts`) to extend the `Vue` instance type: ```typescript declare module 'vue/types/vue' { interface Vue { vueMenuAim: (menuElement: HTMLElement, options: any) => void; } } ``` This explicitly tells TypeScript that `this.vueMenuAim` is available.
Warnings
- breaking This package is designed for Vue 2 applications and is not compatible with Vue 3. Vue 2 reached its End of Life (EOL) on December 31, 2023, meaning it no longer receives official updates or security fixes. Using `vue-menu-aim` in a Vue 3 project will result in runtime errors due to fundamental API changes.
- gotcha When integrating with component libraries like Vuetify, especially `v-menu` or similar components, the `top` and `left` options in the `vueMenuAim` configuration may need to be explicitly set. These values define the position of the main menu, which is crucial for calculating the 'forgiving triangle' accurately. Relying solely on default `offsetTop` and `offsetLeft` might lead to incorrect mouse tracking if the menu is positioned dynamically or is inside other transformed elements.
- deprecated The package has not been updated since May 2019, indicating it is abandoned. There will be no further feature development, bug fixes, or security patches. Its reliance on Vue 2 means it is built against an unsupported framework version. Consider that any issues encountered will likely require a custom fix.
Install
-
npm install vue-menu-aim -
yarn add vue-menu-aim -
pnpm add vue-menu-aim
Imports
- vueMenuAim
import { vueMenuAim } from 'vue-menu-aim';import vueMenuAim from 'vue-menu-aim';
- Vue.use(vueMenuAim)
import { Vue } from 'vue'; Vue.use(vueMenuAim);import vueMenuAim from 'vue-menu-aim'; Vue.use(vueMenuAim);
- vueMenuAim (CommonJS)
const vueMenuAim = require('vue-menu-aim');
Quickstart
import Vue from 'vue';
import vueMenuAim from 'vue-menu-aim';
Vue.use(vueMenuAim);
export default {
data() {
return {
catalog: [
{ id: 1, title: 'Category 1' },
{ id: 2, title: 'Category 2' }
]
};
},
mounted() {
// Example of delayed initialization or for non-SSR scenarios
this.$nextTick(() => {
this.initializeMenuAim();
});
},
updated() {
// Re-initialize if menu structure might change, e.g., in v-for loops
this.initializeMenuAim();
},
methods: {
initializeMenuAim() {
const menuElements = document.querySelectorAll(".menu-aim");
if (menuElements.length === 0) return;
// Assume an activator exists for context, e.g., a Vuetify v-menu activator
const activator = document.querySelector(".v-menu__activator") || { offsetHeight: 0, offsetLeft: 0 };
menuElements.forEach(menu => {
this.vueMenuAim(menu, {
rowSelector: ".menu-li", // Selector for individual menu items
top: activator.offsetHeight,
left: activator.offsetLeft,
activate: row => {
console.log('Activating row:', row.textContent);
// Logic to show a submenu for the activated row
// e.g., add 'active' class to row, display corresponding submenu
},
deactivate: row => {
console.log('Deactivating row:', row.textContent);
// Logic to hide the submenu
}
});
});
}
},
template: `
<div id="app">
<div class="v-menu__activator" style="position: absolute; top: 10px; left: 10px; width: 50px; height: 30px; background-color: lightblue;">Menu Trigger</div>
<v-list class="menu-aim" style="position: absolute; top: 40px; left: 10px; border: 1px solid #ccc; width: 200px;">
<template v-for="element in catalog">
<div :key="element.title" class="menu-li" style="padding: 8px; border-bottom: 1px solid #eee; cursor: pointer;">
<div class="main-group-title">{{ element.title }}</div>
<div class="submenu-content" style="position: absolute; left: 200px; top: 0; background: #f9f9f9; border: 1px solid #ddd; padding: 10px; display: none;">Submenu for {{ element.title }}</div>
</div>
</template>
</v-list>
</div>
`
};