PageSourceSearch

https://indacloud.co/wp-content/themes/hello-theme-child-master/mobile-filters-assets/jquery.menu-aim.js

js indacloud.co collected 2026-09-24 10:40:33 UTC 12,369 bytes, 324 lines download raw bytes

vendor: 12,369 bytes, lines 1-324
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 * MIT License
72*/
73(function($) {
74
75    $.fn.menuAim = function(opts) {
76        // Initialize menu-aim for all elements in jQuery collection
77        this.each(function() {
78            init.call(this, opts);
79        });
80
81        return this;
82    };
83
84    function init(opts) {
85        var $menu = $(this),
86            activeRow = null,
87            mouseLocs = [],
88            lastDelayLoc = null,
89            timeoutId = null,
90            options = $.extend({
91                rowSelector: "> li",
92                submenuSelector: "*",
93                submenuDirection: "right",
94                tolerance: 75,  // bigger = more forgivey when entering submenu
95                enter: $.noop,
96                exit: $.noop,
97                activate: $.noop,
98                deactivate: $.noop,
99                exitMenu: $.noop
100            }, opts);
101
102        var MOUSE_LOCS_TRACKED = 3,  // number of past mouse locations to track
103            DELAY = 300;  // ms delay when user appears to be entering submenu
104
105        /**
106         * Keep track of the last few locations of the mouse.
107         */
108        var mousemoveDocument = function(e) {
109                mouseLocs.push({x: e.pageX, y: e.pageY});
110
111                if (mouseLocs.length > MOUSE_LOCS_TRACKED) {
112                    mouseLocs.shift();
113                }
114            };
115
116        /**
117         * Cancel possible row activations when leaving the menu entirely
118         */
119        var mouseleaveMenu = function() {
120                if (timeoutId) {
121                    clearTimeout(timeoutId);
122                }
123
124                // If exitMenu is supplied and returns true, deactivate the
125                // currently active row on menu exit.
126                if (options.exitMenu(this)) {
127                    if (activeRow) {
128                        options.deactivate(activeRow);
129                    }
130
131                    activeRow = null;
132                }
133            };
134
135        /**
136         * Trigger a possible row activation whenever entering a new row.
137         */
138        var mouseenterRow = function() {
139                if (timeoutId) {
140                    // Cancel any previous activation delays
141                    clearTimeout(timeoutId);
142                }
143
144                options.enter(this);
145                possiblyActivate(this);
146            },
147            mouseleaveRow = function() {
148                options.exit(this);
149            };
150
151        /*
152         * Immediately activate a row if the user clicks on it.
153         */
154        var clickRow = function() {
155                activate(this);
156            };
157
158        /**
159         * Activate a menu row.
160         */
161        var activate = function(row) {
162                if (row == activeRow) {
163                    return;
164                }
165
166                if (activeRow) {
167                    options.deactivate(activeRow);
168                }
169
170                options.activate(row);
171                activeRow = row;
172            };
173
174        /**
175         * Possibly activate a menu row. If mouse movement indicates that we
176         * shouldn't activate yet because user may be trying to enter
177         * a submenu's content, then delay and check again later.
178         */
179        var possiblyActivate = function(row) {
180                var delay = activationDelay();
181
182                if (delay) {
183                    timeoutId = setTimeout(function() {
184                        possiblyActivate(row);
185                    }, delay);
186                } else {
187                    activate(row);
188                }
189            };
190
191        /**
192         * Return the amount of time that should be used as a delay before the
193         * currently hovered row is activated.
194         *
195         * Returns 0 if the activation should happen immediately. Otherwise,
196         * returns the number of milliseconds that should be delayed before
197         * checking again to see if the row should be activated.
198         */
199        var activationDelay = function() {
200                if (!activeRow || !$(activeRow).is(options.submenuSelector)) {
201                    // If there is no other submenu row already active, then
202                    // go ahead and activate immediately.
203                    return 0;
204                }
205
206                var offset = $menu.offset(),
207                    upperLeft = {
208                        x: offset.left,
209                        y: offset.top - options.tolerance
210                    },
211                    upperRight = {
212                        x: offset.left + $menu.outerWidth(),
213                        y: upperLeft.y
214                    },
215                    lowerLeft = {
216                        x: offset.left,
217                        y: offset.top + $menu.outerHeight() + options.tolerance
218                    },
219                    lowerRight = {
220                        x: offset.left + $menu.outerWidth(),
221                        y: lowerLeft.y
222                    },
223                    loc = mouseLocs[mouseLocs.length - 1],
224                    prevLoc = mouseLocs[0];
225
226                if (!loc) {
227                    return 0;
228                }
229
230                if (!prevLoc) {
231                    prevLoc = loc;
232                }
233
234                if (prevLoc.x < offset.left || prevLoc.x > lowerRight.x ||
235                    prevLoc.y < offset.top || prevLoc.y > lowerRight.y) {
236                    // If the previous mouse location was outside of the entire
237                    // menu's bounds, immediately activate.
238                    return 0;
239                }
240
241                if (lastDelayLoc &&
242                        loc.x == lastDelayLoc.x && loc.y == lastDelayLoc.y) {
243                    // If the mouse hasn't moved since the last time we checked
244                    // for activation status, immediately activate.
245                    return 0;
246                }
247
248                // Detect if the user is moving towards the currently activated
249                // submenu.
250                //
251                // If the mouse is heading relatively clearly towards
252                // the submenu's content, we should wait and give the user more
253                // time before activating a new row. If the mouse is heading
254                // elsewhere, we can immediately activate a new row.
255                //
256                // We detect this by calculating the slope formed between the
257                // current mouse location and the upper/lower right points of
258                // the menu. We do the same for the previous mouse location.
259                // If the current mouse location's slopes are
260                // increasing/decreasing appropriately compared to the
261                // previous's, we know the user is moving toward the submenu.
262                //
263                // Note that since the y-axis increases as the cursor moves
264                // down the screen, we are looking for the slope between the
265                // cursor and the upper right corner to decrease over time, not
266                // increase (somewhat counterintuitively).
267                function slope(a, b) {
268                    return (b.y - a.y) / (b.x - a.x);
269                };
270
271                var decreasingCorner = upperRight,
272                    increasingCorner = lowerRight;
273
274                // Our expectations for decreasing or increasing slope values
275                // depends on which direction the submenu opens relative to the
276                // main menu. By default, if the menu opens on the right, we
277                // expect the slope between the cursor and the upper right
278                // corner to decrease over time, as explained above. If the
279                // submenu opens in a different direction, we change our slope
280                // expectations.
281                if (options.submenuDirection == "left") {
282                    decreasingCorner = lowerLeft;
283                    increasingCorner = upperLeft;
284                } else if (options.submenuDirection == "below") {
285                    decreasingCorner = lowerRight;
286                    increasingCorner = lowerLeft;
287                } else if (options.submenuDirection == "above") {
288                    decreasingCorner = upperLeft;
289                    increasingCorner = upperRight;
290                }
291
292                var decreasingSlope = slope(loc, decreasingCorner),
293                    increasingSlope = slope(loc, increasingCorner),
294                    prevDecreasingSlope = slope(prevLoc, decreasingCorner),
295                    prevIncreasingSlope = slope(prevLoc, increasingCorner);
296
297                if (decreasingSlope < prevDecreasingSlope &&
298                        increasingSlope > prevIncreasingSlope) {
299                    // Mouse is moving from previous location towards the
300                    // currently activated submenu. Delay before activating a
301                    // new menu row, because user may be moving into submenu.
302                    lastDelayLoc = loc;
303                    return DELAY;
304                }
305
306                lastDelayLoc = null;
307                return 0;
308            };
309
310        /**
311         * Hook up initial menu events
312         */
313        $menu
314            .mouseleave(mouseleaveMenu)
315            .find(options.rowSelector)
316                .mouseenter(mouseenterRow)
317                .mouseleave(mouseleaveRow)
318                .click(clickRow);
319
320        $(document).mousemove(mousemoveDocument);
321
322    };
323})(jQuery);
324

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.