From 13886b2eb54351a1ae661e2402ecc7f644f413ef Mon Sep 17 00:00:00 2001 From: jlukic Date: Wed, 21 Jan 2015 12:47:49 -0500 Subject: [PATCH] Rewrite dropdown keyboard shortcuts to support sub-menus --- RELEASE-NOTES.md | 3 + src/definitions/modules/dropdown.js | 172 +++++++++++++++++----------- 2 files changed, 111 insertions(+), 64 deletions(-) diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 30d54104e..43dd687a2 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -4,12 +4,15 @@ **Enhancements** +- **Dropdown** - Keyboard navigation will now allow opening of sub menus with right/left arrow. Enter will open sub-menus on an unselectable category (`allowCategorySelection: false`) as well. - **API** - Added new behavior `$.api('abort')` which cancels current request - **Search** - Search `onSelect` now recieves JSON object matching currently selected element, you can now programmatically retrieve result JSON using `.search('get result')`. Defaults to current value unless value specified as first parameter. - **Search** - Search `onSelect` and `onResultsAdd` can now cancel default actions by returning `false`. - **Search** - Greatly reduced search delay from `300ms` to `100ms`. Previous request will automatically abort `xhr` when new request made **Bugs** +- **Dropdown** - Fixed bug where link items would not open in sub-menus due to `event.preventDefault` +- **Dropdown** - Dropdown no longer will not show menu when no `item` are present in menu. Dropdown will now only filter results for `ui search dropdown` #1632 **Thanks PSyton**. - **List** - `relaxed list` and `very relaxed list` no longer add padding to child menu items - **Button** - Fixes formatting for `disabled button` inside `ui buttons` - **Button** - ``ui vertical basic buttons` now have dividers in default theme diff --git a/src/definitions/modules/dropdown.js b/src/definitions/modules/dropdown.js index e80a0843c..342889b1a 100644 --- a/src/definitions/modules/dropdown.js +++ b/src/definitions/modules/dropdown.js @@ -468,77 +468,114 @@ $.fn.dropdown = function(parameters) { }, keydown: function(event) { var - $selectedItem = $item.not(className.filtered).filter('.' + className.selected).eq(0), - $visibleItems = $item.not('.' + className.filtered), + $currentlySelected = $item.not(className.filtered).filter('.' + className.selected).eq(0), + $activeItem = $item.filter('.' + className.active).eq(0), + $selectedItem = ($currentlySelected.length > 0) + ? $currentlySelected + : $activeItem, + $visibleItems = ($selectedItem.length > 0) + ? $selectedItem.siblings(':not(.' + className.filtered +')').andSelf() + : $menu.children(':not(.' + className.filtered +')'), + $subMenu = $selectedItem.children(selector.menu), + $parentMenu = $selectedItem.closest(selector.menu), + isSubMenuItem = $parentMenu[0] !== $menu[0], pressedKey = event.which, keys = { - enter : 13, - escape : 27, - upArrow : 38, - downArrow : 40 + enter : 13, + escape : 27, + leftArrow : 37, + upArrow : 38, + rightArrow : 39, + downArrow : 40 }, - selectedClass = className.selected, - currentIndex = $visibleItems.index( $selectedItem ), - hasSelectedItem = ($selectedItem.length > 0), + hasSubMenu = ($subMenu.length> 0), + hasSelectedItem = ($selectedItem.length > 0), + lastVisibleIndex = ($visibleItems.size() - 1), $nextItem, newIndex ; - // default to activated choice if no selection present - if(!hasSelectedItem) { - $selectedItem = $item.filter('.' + className.active).eq(0); - hasSelectedItem = ($selectedItem.length > 0); - } - // close shortcuts - if(pressedKey == keys.escape) { - module.verbose('Escape key pressed, closing dropdown'); - module.hide(); - } - // open menu - if(pressedKey == keys.downArrow) { - module.verbose('Down key pressed, showing dropdown'); - module.show(); - } - // result shortcuts + // visible menu keyboard shortcuts if(module.is.visible()) { + // enter (select or sub-menu) if(pressedKey == keys.enter && hasSelectedItem) { - module.verbose('Enter key pressed, choosing selected item'); - module.event.item.click.call($selectedItem, event); + if(hasSubMenu && !settings.allowCategorySelection) { + module.verbose('Pressed enter on unselectable category, opening sub menu'); + pressedKey = keys.rightArrow; + } + else { + module.verbose('Enter key pressed, choosing selected item'); + module.event.item.click.call($selectedItem, event); + } + } + // left arrow (hide sub-menu) + if(pressedKey == keys.leftArrow) { + if(isSubMenuItem) { + module.verbose('Left key pressed, closing sub-menu'); + module.animate.hide(false, $parentMenu); + $selectedItem + .removeClass(className.selected) + ; + $parentMenu + .closest(selector.item) + .addClass(className.selected) + ; + } event.preventDefault(); - return false; } - else if(pressedKey == keys.upArrow) { - if(!hasSelectedItem) { - $nextItem = $visibleItems.eq(0); + // right arrow (show sub-menu) + if(pressedKey == keys.rightArrow) { + if(hasSubMenu) { + module.verbose('Right key pressed, opening sub-menu'); + module.animate.show(false, $subMenu); + $selectedItem + .removeClass(className.selected) + ; + $subMenu + .find(selector.item).eq(0) + .addClass(className.selected) + ; } - else { - $nextItem = $selectedItem.prevAll(selector.item + ':not(.' + className.filtered + ')').eq(0); + event.preventDefault(); + } + // up arrow (traverse menu up) + if(pressedKey == keys.upArrow) { + $nextItem = (hasSelectedItem) + ? $selectedItem.prevAll(selector.item + ':not(.' + className.filtered + ')').eq(0) + : $visibleItems.eq(0) + ; + if($visibleItems.index( $nextItem ) < 0) { + module.verbose('Up key pressed but reached top of current menu'); + return; } - if(currentIndex !== 0) { + else { module.verbose('Up key pressed, changing active item'); - $item - .removeClass(selectedClass) + $selectedItem + .removeClass(className.selected) ; $nextItem - .addClass(selectedClass) + .addClass(className.selected) ; module.set.scrollPosition($nextItem); } event.preventDefault(); } - else if(pressedKey == keys.downArrow) { - if(!hasSelectedItem) { - $nextItem = $visibleItems.eq(0); + // down arrow (traverse menu down) + if(pressedKey == keys.downArrow) { + $nextItem = (hasSelectedItem) + ? $nextItem = $selectedItem.nextAll(selector.item + ':not(.' + className.filtered + ')').eq(0) + : $visibleItems.eq(0) + ; + if($nextItem.length === 0) { + module.verbose('Down key pressed but reached bottom of current menu'); + return; } else { - $nextItem = $selectedItem.nextAll(selector.item + ':not(.' + className.filtered + ')').eq(0); - } - if(currentIndex + 1 < $visibleItems.length ) { module.verbose('Down key pressed, changing active item'); $item - .removeClass(selectedClass) + .removeClass(className.selected) ; $nextItem - .addClass(selectedClass) + .addClass(className.selected) ; module.set.scrollPosition($nextItem); } @@ -546,7 +583,19 @@ $.fn.dropdown = function(parameters) { } } else { + // enter (open menu) if(pressedKey == keys.enter) { + module.verbose('Enter key pressed, showing dropdown'); + module.show(); + } + // escape (close menu) + if(pressedKey == keys.escape) { + module.verbose('Escape key pressed, closing dropdown'); + module.hide(); + } + // down arrow (open menu) + if(pressedKey == keys.downArrow) { + module.verbose('Down key pressed, showing dropdown'); module.show(); } } @@ -584,35 +633,33 @@ $.fn.dropdown = function(parameters) { item: { mouseenter: function(event) { var - $currentMenu = $(this).children(selector.menu), - $otherMenus = $(this).siblings(selector.item).children(selector.menu) + $subMenu = $(this).children(selector.menu), + $otherMenus = $(this).siblings(selector.item).children(selector.menu) ; - if( $currentMenu.length > 0 ) { + if( $subMenu.length > 0 ) { clearTimeout(module.itemTimer); module.itemTimer = setTimeout(function() { + module.verbose('Showing sub-menu', $subMenu); $.each($otherMenus, function() { module.animate.hide(false, $(this)); }); - module.verbose('Showing sub-menu', $currentMenu); - module.animate.show(false, $currentMenu); + module.animate.show(false, $subMenu); }, settings.delay.show); event.preventDefault(); } }, - mouseleave: function(event) { var - $currentMenu = $(this).children(selector.menu) + $subMenu = $(this).children(selector.menu) ; - if($currentMenu.length > 0) { + if($subMenu.length > 0) { clearTimeout(module.itemTimer); module.itemTimer = setTimeout(function() { - module.verbose('Hiding sub-menu', $currentMenu); - module.animate.hide(false, $currentMenu); + module.verbose('Hiding sub-menu', $subMenu); + module.animate.hide(false, $subMenu); }, settings.delay.hide); } }, - click: function (event) { var $choice = $(this), @@ -624,23 +671,20 @@ $.fn.dropdown = function(parameters) { module.remove.searchTerm(); module.determine.selectAction(text, value); }, - openingSubMenu = ($subMenu.length > 0), - isSubItem = ($subMenu.find($target).length > 0) + hasSubMenu = ($subMenu.length > 0), + isSubMenuItem = ($subMenu.find($target).length > 0) ; - if(isSubItem) { + if(isSubMenuItem) { return false; } - if(!openingSubMenu || settings.allowCategorySelection) { + if(!hasSubMenu || settings.allowCategorySelection) { callback(); } } - }, - resetStyle: function() { $(this).removeAttr('style'); } - }, determine: { @@ -1199,8 +1243,8 @@ $.fn.dropdown = function(parameters) { ? 'slide up' : 'slide down' ; + module.verbose('Automatically determining animation based on animation direction', settings.transition); } - if(settings.transition == 'none') { callback.call(element); }