WordPress.org

Make WordPress Core

Ticket #23731: 23731.2.patch

File 23731.2.patch, 13.4 KB (added by ocean90, 5 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){