PageSourceSearch

https://lojagraficaeskenazi.com.br/files/skins/fbf872e0-2e5f-4649-8155-3541210957fa/js/jquery.menu-aim.js

js lojagraficaeskenazi.com.br collected 2026-09-26 12:27:34 UTC 12,354 bytes, 323 lines download raw bytes

vendor: 12,354 bytes, lines 1-323
1/**
2 * menu-aim is a jQuery plugin for dropdown menus that can differentiate
3 * between a user trying hover over a dropdown item vs trying to navigate into
4 * a submenu's contents.
5 *
6 * menu-aim assumes that you have are using a menu with submenus that expand
7 * to the menu's right. It will fire events when the user's mouse enters a new
8 * dropdown item *and* when that item is being intentionally hovered over.
9 *
10 * __________________________
11 * | Monkeys  >|   Gorilla  |
12 * | Gorillas >|   Content  |
13 * | Chimps   >|   Here     |
14 * |___________|____________|
15 *
16 * In the above example, "Gorillas" is selected and its submenu content is
17 * being shown on the right. Imagine that the user's cursor is hovering over
18 * "Gorillas." When they move their mouse into the "Gorilla Content" area, they
19 * may briefly hover over "Chimps." This shouldn't close the "Gorilla Content"
20 * area.
21 *
22 * This problem is normally solved using timeouts and delays. menu-aim tries to
23 * solve this by detecting the direction of the user's mouse movement. This can
24 * make for quicker transitions when navigating up and down the menu. The
25 * experience is hopefully similar to amazon.com/'s "Shop by Department"
26 * dropdown.
27 *
28 * Use like so:
29 *
30 *      $("#menu").menuAim({
31 *          activate: $.noop,  // fired on row activation
32 *          deactivate: $.noop  // fired on row deactivation
33 *      });
34 *
35 *  ...to receive events when a menu's row has been purposefully (de)activated.
36 *
37 * The following options can be passed to menuAim. All functions execute with
38 * the relevant row's HTML element as the execution context ('this'):
39 *
40 *      .menuAim({
41 *          // Function to call when a row is purposefully activated. Use this
42 *          // to show a submenu's content for the activated row.
43 *          activate: function() {},
44 *
45 *          // Function to call when a row is deactivated.
46 *          deactivate: function() {},
47 *
48 *          // Function to call when mouse enters a menu row. Entering a row
49 *          // does not mean the row has been activated, as the user may be
50 *          // mousing over to a submenu.
51 *          enter: function() {},
52 *
53 *          // Function to call when mouse exits a menu row.
54 *          exit: function() {},
55 *
56 *          // Selector for identifying which elements in the menu are rows
57 *          // that can trigger the above events. Defaults to "> li".
58 *          rowSelector: "> li",
59 *
60 *          // You may have some menu rows that aren't submenus and therefore
61 *          // shouldn't ever need to "activate." If so, filter submenu rows w/
62 *          // this selector. Defaults to "*" (all elements).
63 *          submenuSelector: "*",
64 *
65 *          // Direction the submenu opens relative to the main menu. Can be
66 *          // left, right, above, or below. Defaults to "right".
67 *          submenuDirection: "right"
68 *      });
69 *
70 * https://github.com/kamens/jQuery-menu-aim
71*/
72(function($) {
73
74    $.fn.menuAim = function(opts) {
75        // Initialize menu-aim for all elements in jQuery collection
76        this.each(function() {
77            init.call(this, opts);
78        });
79
80        return this;
81    };
82
83    function init(opts) {
84        var $menu = $(this),
85            activeRow = null,
86            mouseLocs = [],
87            lastDelayLoc = null,
88            timeoutId = null,
89            options = $.extend({
90                rowSelector: "> li",
91                submenuSelector: "*",
92                submenuDirection: "right",
93                tolerance: 75,  // bigger = more forgivey when entering submenu
94                enter: $.noop,
95                exit: $.noop,
96                activate: $.noop,
97                deactivate: $.noop,
98                exitMenu: $.noop
99            }, opts);
100
101        var MOUSE_LOCS_TRACKED = 3,  // number of past mouse locations to track
102            DELAY = 300;  // ms delay when user appears to be entering submenu
103
104        /**
105         * Keep track of the last few locations of the mouse.
106         */
107        var mousemoveDocument = function(e) {
108                mouseLocs.push({x: e.pageX, y: e.pageY});
109
110                if (mouseLocs.length > MOUSE_LOCS_TRACKED) {
111                    mouseLocs.shift();
112                }
113            };
114
115        /**
116         * Cancel possible row activations when leaving the menu entirely
117         */
118        var mouseleaveMenu = function() {
119                if (timeoutId) {
120                    clearTimeout(timeoutId);
121                }
122
123                // If exitMenu is supplied and returns true, deactivate the
124                // currently active row on menu exit.
125                if (options.exitMenu(this)) {
126                    if (activeRow) {
127                        options.deactivate(activeRow);
128                    }
129
130                    activeRow = null;
131                }
132            };
133
134        /**
135         * Trigger a possible row activation whenever entering a new row.
136         */
137        var mouseenterRow = function() {
138                if (timeoutId) {
139                    // Cancel any previous activation delays
140                    clearTimeout(timeoutId);
141                }
142
143                options.enter(this);
144                possiblyActivate(this);
145            },
146            mouseleaveRow = function() {
147                options.exit(this);
148            };
149
150        /*
151         * Immediately activate a row if the user clicks on it.
152         */
153        var clickRow = function() {
154                activate(this);
155            };
156
157        /**
158         * Activate a menu row.
159         */
160        var activate = function(row) {
161                if (row == activeRow) {
162                    return;
163                }
164
165                if (activeRow) {
166                    options.deactivate(activeRow);
167                }
168
169                options.activate(row);
170                activeRow = row;
171            };
172
173        /**
174         * Possibly activate a menu row. If mouse movement indicates that we
175         * shouldn't activate yet because user may be trying to enter
176         * a submenu's content, then delay and check again later.
177         */
178        var possiblyActivate = function(row) {
179                var delay = activationDelay();
180
181                if (delay) {
182                    timeoutId = setTimeout(function() {
183                        possiblyActivate(row);
184                    }, delay);
185                } else {
186                    activate(row);
187                }
188            };
189
190        /**
191         * Return the amount of time that should be used as a delay before the
192         * currently hovered row is activated.
193         *
194         * Returns 0 if the activation should happen immediately. Otherwise,
195         * returns the number of milliseconds that should be delayed before
196         * checking again to see if the row should be activated.
197         */
198        var activationDelay = function() {
199                if (!activeRow || !$(activeRow).is(options.submenuSelector)) {
200                    // If there is no other submenu row already active, then
201                    // go ahead and activate immediately.
202                    return 0;
203                }
204
205                var offset = $menu.offset(),
206                    upperLeft = {
207                        x: offset.left,
208                        y: offset.top - options.tolerance
209                    },
210                    upperRight = {
211                        x: offset.left + $menu.outerWidth(),
212                        y: upperLeft.y
213                    },
214                    lowerLeft = {
215                        x: offset.left,
216                        y: offset.top + $menu.outerHeight() + options.tolerance
217                    },
218                    lowerRight = {
219                        x: offset.left + $menu.outerWidth(),
220                        y: lowerLeft.y
221                    },
222                    loc = mouseLocs[mouseLocs.length - 1],
223                    prevLoc = mouseLocs[0];
224
225                if (!loc) {
226                    return 0;
227                }
228
229                if (!prevLoc) {
230                    prevLoc = loc;
231                }
232
233                if (prevLoc.x < offset.left || prevLoc.x > lowerRight.x ||
234                    prevLoc.y < offset.top || prevLoc.y > lowerRight.y) {
235                    // If the previous mouse location was outside of the entire
236                    // menu's bounds, immediately activate.
237                    return 0;
238                }
239
240                if (lastDelayLoc &&
241                        loc.x == lastDelayLoc.x && loc.y == lastDelayLoc.y) {
242                    // If the mouse hasn't moved since the last time we checked
243                    // for activation status, immediately activate.
244                    return 0;
245                }
246
247                // Detect if the user is moving towards the currently activated
248                // submenu.
249                //
250                // If the mouse is heading relatively clearly towards
251                // the submenu's content, we should wait and give the user more
252                // time before activating a new row. If the mouse is heading
253                // elsewhere, we can immediately activate a new row.
254                //
255                // We detect this by calculating the slope formed between the
256                // current mouse location and the upper/lower right points of
257                // the menu. We do the same for the previous mouse location.
258                // If the current mouse location's slopes are
259                // increasing/decreasing appropriately compared to the
260                // previous's, we know the user is moving toward the submenu.
261                //
262                // Note that since the y-axis increases as the cursor moves
263                // down the screen, we are looking for the slope between the
264                // cursor and the upper right corner to decrease over time, not
265                // increase (somewhat counterintuitively).
266                function slope(a, b) {
267                    return (b.y - a.y) / (b.x - a.x);
268                };
269
270                var decreasingCorner = upperRight,
271                    increasingCorner = lowerRight;
272
273                // Our expectations for decreasing or increasing slope values
274                // depends on which direction the submenu opens relative to the
275                // main menu. By default, if the menu opens on the right, we
276                // expect the slope between the cursor and the upper right
277                // corner to decrease over time, as explained above. If the
278                // submenu opens in a different direction, we change our slope
279                // expectations.
280                if (options.submenuDirection == "left") {
281                    decreasingCorner = lowerLeft;
282                    increasingCorner = upperLeft;
283                } else if (options.submenuDirection == "below") {
284                    decreasingCorner = lowerRight;
285                    increasingCorner = lowerLeft;
286                } else if (options.submenuDirection == "above") {
287                    decreasingCorner = upperLeft;
288                    increasingCorner = upperRight;
289                }
290
291                var decreasingSlope = slope(loc, decreasingCorner),
292                    increasingSlope = slope(loc, increasingCorner),
293                    prevDecreasingSlope = slope(prevLoc, decreasingCorner),
294                    prevIncreasingSlope = slope(prevLoc, increasingCorner);
295
296                if (decreasingSlope < prevDecreasingSlope &&
297                        increasingSlope > prevIncreasingSlope) {
298                    // Mouse is moving from previous location towards the
299                    // currently activated submenu. Delay before activating a
300                    // new menu row, because user may be moving into submenu.
301                    lastDelayLoc = loc;
302                    return DELAY;
303                }
304
305                lastDelayLoc = null;
306                return 0;
307            };
308
309        /**
310         * Hook up initial menu events
311         */
312        $menu
313            .mouseleave(mouseleaveMenu)
314            .find(options.rowSelector)
315                .mouseenter(mouseenterRow)
316                .mouseleave(mouseleaveRow)
317                .click(clickRow);
318
319        $(document).mousemove(mousemoveDocument);
320
321    };
322})(jQuery);
323

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.