{"version":3,"file":"../multidropdown.js","names":["$","init","opts","$menu","this","activeRow","mouseLocs","lastDelayLoc","timeoutId","options","extend","rowSelector","submenuSelector","submenuDirection","tolerance","enter","noop","exit","activate","deactivate","exitMenu","possiblyActivate","row","delay","activationDelay","setTimeout","is","offset","upperLeft","x","left","y","top","upperRight","outerWidth","lowerLeft","outerHeight","lowerRight","loc","length","prevLoc","slope","a","b","decreasingCorner","increasingCorner","decreasingSlope","increasingSlope","prevDecreasingSlope","prevIncreasingSlope","mouseleave","clearTimeout","find","mouseenter","document","mousemove","e","push","pageX","pageY","shift","fn","menuAim","each","call","jQuery"],"sources":["multidropdown.js"],"mappings":"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","sourcesContent":["/**\n * Use like so:\n *\n * $(\"#menu\").menuAim({\n * activate: $.noop, // fired on row activation\n * deactivate: $.noop // fired on row deactivation\n * });\n *\n * ...to receive events when a menu's row has been purposefully (de)activated.\n *\n * The following options can be passed to menuAim. All functions execute with\n * the relevant row's HTML element as the execution context ('this'):\n *\n * .menuAim({\n * // Function to call when a row is purposefully activated. Use this\n * // to show a submenu's content for the activated row.\n * activate: function() {},\n *\n * // Function to call when a row is deactivated.\n * deactivate: function() {},\n *\n * // Function to call when mouse enters a menu row. Entering a row\n * // does not mean the row has been activated, as the user may be\n * // mousing over to a submenu.\n * enter: function() {},\n *\n * // Function to call when mouse exits a menu row.\n * exit: function() {},\n *\n * // Selector for identifying which elements in the menu are rows\n * // that can trigger the above events. Defaults to \"> li\".\n * rowSelector: \"> li\",\n *\n * // You may have some menu rows that aren't submenus and therefore\n * // shouldn't ever need to \"activate.\" If so, filter submenu rows w/\n * // this selector. Defaults to \"*\" (all elements).\n * submenuSelector: \"*\",\n *\n * // Direction the submenu opens relative to the main menu. Can be\n * // left, right, above, or below. Defaults to \"right\".\n * submenuDirection: \"right\"\n * });\n *\n *\n*/\n(function($) {\n\n $.fn.menuAim = function(opts) {\n // Initialize menu-aim for all elements in jQuery collection\n this.each(function() {\n init.call(this, opts);\n });\n\n return this;\n };\n\n function init(opts) {\n var $menu = $(this),\n activeRow = null,\n mouseLocs = [],\n lastDelayLoc = null,\n timeoutId = null,\n options = $.extend({\n rowSelector: \"> li\",\n submenuSelector: \"*\",\n submenuDirection: \"right\",\n tolerance: 75, // bigger = more forgivey when entering submenu\n enter: $.noop,\n exit: $.noop,\n activate: $.noop,\n deactivate: $.noop,\n exitMenu: $.noop\n }, opts);\n\n var MOUSE_LOCS_TRACKED = 3, // number of past mouse locations to track\n DELAY = 300; // ms delay when user appears to be entering submenu\n\n /**\n * Keep track of the last few locations of the mouse.\n */\n var mousemoveDocument = function(e) {\n mouseLocs.push({x: e.pageX, y: e.pageY});\n\n if (mouseLocs.length > MOUSE_LOCS_TRACKED) {\n mouseLocs.shift();\n }\n };\n\n /**\n * Cancel possible row activations when leaving the menu entirely\n */\n var mouseleaveMenu = function() {\n if (timeoutId) {\n clearTimeout(timeoutId);\n }\n\n // If exitMenu is supplied and returns true, deactivate the\n // currently active row on menu exit.\n if (options.exitMenu(this)) {\n if (activeRow) {\n options.deactivate(activeRow);\n }\n\n activeRow = null;\n }\n };\n\n /**\n * Trigger a possible row activation whenever entering a new row.\n */\n var mouseenterRow = function() {\n if (timeoutId) {\n // Cancel any previous activation delays\n clearTimeout(timeoutId);\n }\n\n options.enter(this);\n possiblyActivate(this);\n },\n mouseleaveRow = function() {\n options.exit(this);\n };\n\n /**\n * Activate a menu row.\n */\n var activate = function(row) {\n if (row == activeRow) {\n return;\n }\n\n if (activeRow) {\n options.deactivate(activeRow);\n }\n\n options.activate(row);\n activeRow = row;\n };\n\n /**\n * Possibly activate a menu row. If mouse movement indicates that we\n * shouldn't activate yet because user may be trying to enter\n * a submenu's content, then delay and check again later.\n */\n var possiblyActivate = function(row) {\n var delay = activationDelay();\n\n if (delay) {\n timeoutId = setTimeout(function() {\n possiblyActivate(row);\n }, delay);\n } else {\n activate(row);\n }\n };\n\n /**\n * Return the amount of time that should be used as a delay before the\n * currently hovered row is activated.\n *\n * Returns 0 if the activation should happen immediately. Otherwise,\n * returns the number of milliseconds that should be delayed before\n * checking again to see if the row should be activated.\n */\n var activationDelay = function() {\n if (!activeRow || !$(activeRow).is(options.submenuSelector)) {\n // If there is no other submenu row already active, then\n // go ahead and activate immediately.\n return 0;\n }\n\n var offset = $menu.offset(),\n upperLeft = {\n x: offset.left,\n y: offset.top - options.tolerance\n },\n upperRight = {\n x: offset.left + $menu.outerWidth(),\n y: upperLeft.y\n },\n lowerLeft = {\n x: offset.left,\n y: offset.top + $menu.outerHeight() + options.tolerance\n },\n lowerRight = {\n x: offset.left + $menu.outerWidth(),\n y: lowerLeft.y\n },\n loc = mouseLocs[mouseLocs.length - 1],\n prevLoc = mouseLocs[0];\n\n if (!loc) {\n return 0;\n }\n\n if (!prevLoc) {\n prevLoc = loc;\n }\n\n if (prevLoc.x < offset.left || prevLoc.x > lowerRight.x ||\n prevLoc.y < offset.top || prevLoc.y > lowerRight.y) {\n // If the previous mouse location was outside of the entire\n // menu's bounds, immediately activate.\n return 0;\n }\n\n if (lastDelayLoc &&\n loc.x == lastDelayLoc.x && loc.y == lastDelayLoc.y) {\n // If the mouse hasn't moved since the last time we checked\n // for activation status, immediately activate.\n return 0;\n }\n\n // Detect if the user is moving towards the currently activated\n // submenu.\n //\n // If the mouse is heading relatively clearly towards\n // the submenu's content, we should wait and give the user more\n // time before activating a new row. If the mouse is heading\n // elsewhere, we can immediately activate a new row.\n //\n // We detect this by calculating the slope formed between the\n // current mouse location and the upper/lower right points of\n // the menu. We do the same for the previous mouse location.\n // If the current mouse location's slopes are\n // increasing/decreasing appropriately compared to the\n // previous's, we know the user is moving toward the submenu.\n //\n // Note that since the y-axis increases as the cursor moves\n // down the screen, we are looking for the slope between the\n // cursor and the upper right corner to decrease over time, not\n // increase (somewhat counterintuitively).\n function slope(a, b) {\n return (b.y - a.y) / (b.x - a.x);\n };\n\n var decreasingCorner = upperRight,\n increasingCorner = lowerRight;\n\n // Our expectations for decreasing or increasing slope values\n // depends on which direction the submenu opens relative to the\n // main menu. By default, if the menu opens on the right, we\n // expect the slope between the cursor and the upper right\n // corner to decrease over time, as explained above. If the\n // submenu opens in a different direction, we change our slope\n // expectations.\n if (options.submenuDirection == \"left\") {\n decreasingCorner = lowerLeft;\n increasingCorner = upperLeft;\n } else if (options.submenuDirection == \"below\") {\n decreasingCorner = lowerRight;\n increasingCorner = lowerLeft;\n } else if (options.submenuDirection == \"above\") {\n decreasingCorner = upperLeft;\n increasingCorner = upperRight;\n }\n\n var decreasingSlope = slope(loc, decreasingCorner),\n increasingSlope = slope(loc, increasingCorner),\n prevDecreasingSlope = slope(prevLoc, decreasingCorner),\n prevIncreasingSlope = slope(prevLoc, increasingCorner);\n\n if (decreasingSlope < prevDecreasingSlope &&\n increasingSlope > prevIncreasingSlope) {\n // Mouse is moving from previous location towards the\n // currently activated submenu. Delay before activating a\n // new menu row, because user may be moving into submenu.\n lastDelayLoc = loc;\n return DELAY;\n }\n\n lastDelayLoc = null;\n return 0;\n };\n\n /**\n * Hook up initial menu events\n */\n $menu\n .mouseleave(mouseleaveMenu)\n .find(options.rowSelector)\n .mouseenter(mouseenterRow)\n .mouseleave(mouseleaveRow);\n $(document).mousemove(mousemoveDocument);\n\n };\n})(jQuery);\n\n"]}