UNPKG

jquery.tabbable

Version:

Simple utility for selecting the next / previous 'tabbable' element. Includes and uses the ':tabbable' and ':focusable' selectors from jQuery UI Core.

137 lines (123 loc) 4.11 kB
/*! * jQuery.tabbable 1.0 - Simple utility for selecting the next / previous ':tabbable' element. * https://github.com/marklagendijk/jQuery.tabbable * * Includes ':tabbable' and ':focusable' selectors from jQuery UI Core * * Copyright 2013, Mark Lagendijk * Released under the MIT license * */ (function($){ 'use strict'; /** * Focusses the next :focusable element. Elements with tabindex=-1 are focusable, but not tabable. * Does not take into account that the taborder might be different as the :tabbable elements order * (which happens when using tabindexes which are greater than 0). */ $.focusNext = function(){ selectNextTabbableOrFocusable(':focusable'); }; /** * Focusses the previous :focusable element. Elements with tabindex=-1 are focusable, but not tabable. * Does not take into account that the taborder might be different as the :tabbable elements order * (which happens when using tabindexes which are greater than 0). */ $.focusPrev = function(){ selectPrevTabbableOrFocusable(':focusable'); }; /** * Focusses the next :tabable element. * Does not take into account that the taborder might be different as the :tabbable elements order * (which happens when using tabindexes which are greater than 0). */ $.tabNext = function(){ selectNextTabbableOrFocusable(':tabbable'); }; /** * Focusses the previous :tabbable element * Does not take into account that the taborder might be different as the :tabbable elements order * (which happens when using tabindexes which are greater than 0). */ $.tabPrev = function(){ selectPrevTabbableOrFocusable(':tabbable'); }; function selectNextTabbableOrFocusable(selector){ var selectables = $(selector); var current = $(':focus'); var nextIndex = 0; if(current.length === 1){ var currentIndex = selectables.index(current); if(currentIndex + 1 < selectables.length){ nextIndex = currentIndex + 1; } } selectables.eq(nextIndex).focus(); } function selectPrevTabbableOrFocusable(selector){ var selectables = $(selector); var current = $(':focus'); var prevIndex = selectables.length - 1; if(current.length === 1){ var currentIndex = selectables.index(current); if(currentIndex > 0){ prevIndex = currentIndex - 1; } } selectables.eq(prevIndex).focus(); } /** * :focusable and :tabbable, both taken from jQuery UI Core */ $.extend($.expr[ ':' ], { data: $.expr.createPseudo ? $.expr.createPseudo(function(dataName){ return function(elem){ return !!$.data(elem, dataName); }; }) : // support: jQuery <1.8 function(elem, i, match){ return !!$.data(elem, match[ 3 ]); }, focusable: function(element){ return focusable(element, !isNaN($.attr(element, 'tabindex'))); }, tabbable: function(element){ var tabIndex = $.attr(element, 'tabindex'), isTabIndexNaN = isNaN(tabIndex); return ( isTabIndexNaN || tabIndex >= 0 ) && focusable(element, !isTabIndexNaN); } }); /** * focussable function, taken from jQuery UI Core * @param element * @returns {*} */ function focusable(element){ var map, mapName, img, nodeName = element.nodeName.toLowerCase(), isTabIndexNotNaN = !isNaN($.attr(element, 'tabindex')); if('area' === nodeName){ map = element.parentNode; mapName = map.name; if(!element.href || !mapName || map.nodeName.toLowerCase() !== 'map'){ return false; } img = $('img[usemap=#' + mapName + ']')[0]; return !!img && visible(img); } return ( /input|select|textarea|button|object/.test(nodeName) ? !element.disabled : 'a' === nodeName ? element.href || isTabIndexNotNaN : isTabIndexNotNaN) && // the element and all of its ancestors must be visible visible(element); function visible(element){ return $.expr.filters.visible(element) && !$(element).parents().addBack().filter(function(){ return $.css(this, 'visibility') === 'hidden'; }).length; } } })(jQuery);