WordPress.org

Make WordPress Core

Ticket #23731: 23731.2.patch

File 23731.2.patch, 13.4 KB (added by ocean90, 2 years ago)
  • wp-includes/js/jquery/jquery.menu-aim.js

     
     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 * 
     66 * https://github.com/kamens/jQuery-menu-aim 
     67*/ 
     68(function($) { 
     69    $.fn.menuAim = function(opts) { 
     70 
     71        var $menu = $(this), 
     72            activeRow = null, 
     73            mouseLocs = [], 
     74            lastDelayLoc = null, 
     75            timeoutId = null, 
     76            options = $.extend({ 
     77                rowSelector: "> li", 
     78                submenuSelector: "*", 
     79                tolerance: 75,  // bigger = more forgivey when entering submenu 
     80                enter: $.noop, 
     81                exit: $.noop, 
     82                activate: $.noop, 
     83                deactivate: $.noop 
     84            }, opts); 
     85 
     86        var MOUSE_LOCS_TRACKED = 3,  // number of past mouse locations to track 
     87            DELAY = 300;  // ms delay when user appears to be entering submenu 
     88 
     89        /** 
     90         * Keep track of the last few locations of the mouse. 
     91         */ 
     92        var mousemoveDocument = function(e) { 
     93                mouseLocs.push({x: e.pageX, y: e.pageY}); 
     94 
     95                if (mouseLocs.length > MOUSE_LOCS_TRACKED) { 
     96                    mouseLocs.shift(); 
     97                } 
     98            }; 
     99 
     100        /** 
     101         * Cancel possible row activations when leaving the menu entirely 
     102         */ 
     103        var mouseleaveMenu = function() { 
     104                if (timeoutId) { 
     105                    clearTimeout(timeoutId); 
     106                } 
     107            }; 
     108 
     109        /** 
     110         * Trigger a possible row activation whenever entering a new row. 
     111         */ 
     112        var mouseenterRow = function() { 
     113                if (timeoutId) { 
     114                    // Cancel any previous activation delays 
     115                    clearTimeout(timeoutId); 
     116                } 
     117 
     118                options.enter(this); 
     119                possiblyActivate(this); 
     120            }, 
     121            mouseleaveRow = function() { 
     122                options.exit(this); 
     123            }; 
     124 
     125        /** 
     126         * Activate a menu row. 
     127         */ 
     128        var activate = function(row) { 
     129                if (row == activeRow) { 
     130                    return; 
     131                } 
     132 
     133                if (activeRow) { 
     134                    options.deactivate(activeRow); 
     135                } 
     136 
     137                options.activate(row); 
     138                activeRow = row; 
     139            }; 
     140 
     141        /** 
     142         * Possibly activate a menu row. If mouse movement indicates that we 
     143         * shouldn't activate yet because user may be trying to enter 
     144         * a submenu's content, then delay and check again later. 
     145         */ 
     146        var possiblyActivate = function(row) { 
     147                var delay = activationDelay(); 
     148 
     149                if (delay) { 
     150                    timeoutId = setTimeout(function() { 
     151                        possiblyActivate(row); 
     152                    }, delay); 
     153                } else { 
     154                    activate(row); 
     155                } 
     156            }; 
     157 
     158        /** 
     159         * Return the amount of time that should be used as a delay before the 
     160         * currently hovered row is activated. 
     161         * 
     162         * Returns 0 if the activation should happen immediately. Otherwise, 
     163         * returns the number of milliseconds that should be delayed before 
     164         * checking again to see if the row should be activated. 
     165         */ 
     166        var activationDelay = function() { 
     167                if (!activeRow || !$(activeRow).is(options.submenuSelector)) { 
     168                    // If there is no other submenu row already active, then 
     169                    // go ahead and activate immediately. 
     170                    return 0; 
     171                } 
     172 
     173                var offset = $menu.offset(), 
     174                    upperRight = { 
     175                        x: offset.left + $menu.outerWidth(), 
     176                        y: offset.top - options.tolerance 
     177                    }, 
     178                    lowerRight = { 
     179                        x: offset.left + $menu.outerWidth(), 
     180                        y: offset.top + $menu.outerHeight() + options.tolerance 
     181                    }, 
     182                    loc = mouseLocs[mouseLocs.length - 1], 
     183                    prevLoc = mouseLocs[0]; 
     184 
     185                if (!loc) { 
     186                    return 0; 
     187                } 
     188 
     189                if (!prevLoc) { 
     190                    prevLoc = loc; 
     191                } 
     192 
     193                if (prevLoc.x < offset.left || prevLoc.x > lowerRight.x || 
     194                    prevLoc.y < offset.top || prevLoc.y > lowerRight.y) { 
     195                    // If the previous mouse location was outside of the entire 
     196                    // menu's bounds, immediately activate. 
     197                    return 0; 
     198                } 
     199 
     200                if (lastDelayLoc && 
     201                        loc.x == lastDelayLoc.x && loc.y == lastDelayLoc.y) { 
     202                    // If the mouse hasn't moved since the last time we checked 
     203                    // for activation status, immediately activate. 
     204                    return 0; 
     205                } 
     206 
     207                // Detect if the user is moving towards the currently activated 
     208                // submenu. 
     209                // 
     210                // If the mouse is heading relatively clearly towards 
     211                // the submenu's content, we should wait and give the user more 
     212                // time before activating a new row. If the mouse is heading 
     213                // elsewhere, we can immediately activate a new row. 
     214                // 
     215                // We detect this by calculating the slope formed between the 
     216                // current mouse location and the upper/lower right points of 
     217                // the menu. We do the same for the previous mouse location. 
     218                // If the current mouse location's slopes are 
     219                // increasing/decreasing appropriately compared to the 
     220                // previous's, we know the user is moving toward the submenu. 
     221                // 
     222                // Note that since the y-axis increases as the cursor moves 
     223                // down the screen, we are looking for the slope between the 
     224                // cursor and the upper right corner to decrease over time, not 
     225                // increase (somewhat counterintuitively). 
     226                function slope(a, b) { 
     227                    return (b.y - a.y) / (b.x - a.x); 
     228                }; 
     229 
     230                var upperSlope = slope(loc, upperRight), 
     231                    lowerSlope = slope(loc, lowerRight), 
     232                    prevUpperSlope = slope(prevLoc, upperRight), 
     233                    prevLowerSlope = slope(prevLoc, lowerRight); 
     234 
     235                if (upperSlope < prevUpperSlope && 
     236                        lowerSlope > prevLowerSlope) { 
     237                    // Mouse is moving from previous location towards the 
     238                    // currently activated submenu. Delay before activating a 
     239                    // new menu row, because user may be moving into submenu. 
     240                    lastDelayLoc = loc; 
     241                    return DELAY; 
     242                } 
     243 
     244                lastDelayLoc = null; 
     245                return 0; 
     246            }; 
     247 
     248        /** 
     249         * Hook up initial menu events 
     250         */ 
     251        var init = function() { 
     252            $menu 
     253                .mouseleave(mouseleaveMenu) 
     254                .find(options.rowSelector) 
     255                    .mouseenter(mouseenterRow) 
     256                    .mouseleave(mouseleaveRow); 
     257 
     258            $(document).mousemove(mousemoveDocument); 
     259        }; 
     260 
     261        init(); 
     262        return this; 
     263    }; 
     264})(jQuery); 
  • wp-includes/script-loader.php

     
    6666                'time' => (string) time(), 
    6767        ) ); 
    6868 
    69         $scripts->add( 'common', "/wp-admin/js/common$suffix.js", array('jquery', 'hoverIntent', 'utils'), false, 1 ); 
     69        $scripts->add( 'common', "/wp-admin/js/common$suffix.js", array('jquery', 'hoverIntent', 'jquery-menu-aim', 'utils'), false, 1 ); 
    7070        did_action( 'init' ) && $scripts->localize( 'common', 'commonL10n', array( 
    7171                'warnDelete' => __("You are about to permanently delete the selected items.\n  'Cancel' to stop, 'OK' to delete.") 
    7272        ) ); 
     
    185185        $scripts->add( 'jquery-table-hotkeys', "/wp-includes/js/jquery/jquery.table-hotkeys$suffix.js", array('jquery', 'jquery-hotkeys'), false, 1 ); 
    186186        $scripts->add( 'jquery-touch-punch', "/wp-includes/js/jquery/jquery.ui.touch-punch.js", array('jquery-ui-widget', 'jquery-ui-mouse'), '0.2.2', 1 ); 
    187187        $scripts->add( 'jquery-masonry', "/wp-includes/js/jquery/jquery.masonry.min.js", array('jquery'), '2.1.05', 1 ); 
     188         
     189        $scripts->add( 'jquery-menu-aim', "/wp-includes/js/jquery/jquery.menu-aim.js", array('jquery'), '1', 1 ); 
    188190 
    189191        $scripts->add( 'thickbox', "/wp-includes/js/thickbox/thickbox.js", array('jquery'), '3.1-20121105', 1 ); 
    190192        did_action( 'init' ) && $scripts->localize( 'thickbox', 'thickboxL10n', array( 
  • wp-admin/js/common.js

     
    218218                        } 
    219219                }); 
    220220        } 
    221  
     221/* 
    222222        menu.find('li.wp-has-submenu').hoverIntent({ 
    223223                over: function(e){ 
    224224                        var b, h, o, f, m = $(this).find('.wp-submenu'), menutop, wintop, maxtop, top = parseInt( m.css('top'), 10 ); 
     
    256256                sensitivity: 7, 
    257257                interval: 90 
    258258        }); 
     259*/ 
    259260 
     261        menu.menuAim({ 
     262                activate: function(row){ 
     263                        var b, h, o, f, m = $(row).find('.wp-submenu'), menutop, wintop, maxtop, top = parseInt( m.css('top'), 10 ); 
     264 
     265                        if ( isNaN(top) || top > -5 ) // meaning the submenu is visible 
     266                                return; 
     267 
     268                        menutop = $(row).offset().top; 
     269                        wintop = $(window).scrollTop(); 
     270                        maxtop = menutop - wintop - 30; // max = make the top of the sub almost touch admin bar 
     271 
     272                        b = menutop + m.height() + 1; // Bottom offset of the menu 
     273                        h = $('#wpwrap').height(); // Height of the entire page 
     274                        o = 60 + b - h; 
     275                        f = $(window).height() + wintop - 15; // The fold 
     276 
     277                        if ( f < (b - o) ) 
     278                                o = b - f; 
     279 
     280                        if ( o > maxtop ) 
     281                                o = maxtop; 
     282 
     283                        if ( o > 1 ) 
     284                                m.css('margin-top', '-'+o+'px'); 
     285                        else 
     286                                m.css('margin-top', ''); 
     287 
     288                        menu.find('li.menu-top').removeClass('opensub'); 
     289                        $(row).addClass('opensub'); 
     290                }, 
     291 
     292                deactivate: function(row) { 
     293                        $(row).removeClass('opensub').find('.wp-submenu').css('margin-top', ''); 
     294                }, 
     295                rowSelector: "> li.menu-top", 
     296                submenuSelector: "*" 
     297        }); 
     298 
     299        menu.on( 'mouseleave', function() { 
     300                $('.opensub', menu).removeClass('opensub'); 
     301        }); 
     302 
    260303        menu.on('focus.adminmenu', '.wp-submenu a', function(e){ 
    261304                $(e.target).closest('li.menu-top').addClass('opensub'); 
    262305        }).on('blur.adminmenu', '.wp-submenu a', function(e){