From fe13fbadd45b59fb67ce6b47c5aea6231596a7c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Scott=20Gonz=C3=A1lez?= Date: Thu, 22 Jul 2010 10:33:42 -0400 Subject: [PATCH] Core: Better support for areas in :focusable and :tabbable selectors. Partial fix for #4488 - :focusable and :tabbable are broken with jQuery 1.3.2. --- tests/unit/core/core.html | 15 +++++++++++---- tests/unit/core/selector.js | 38 ++++++++++++++++++++++--------------- ui/jquery.ui.core.js | 25 ++++++++++++++++++------ 3 files changed, 53 insertions(+), 25 deletions(-) diff --git a/tests/unit/core/core.html b/tests/unit/core/core.html index ee2f5fdfa..ec5577001 100644 --- a/tests/unit/core/core.html +++ b/tests/unit/core/core.html @@ -42,6 +42,17 @@
+ + + + + + + + + + +
@@ -57,10 +68,6 @@ xxx anchor anchor - - - - x
x
x diff --git a/tests/unit/core/selector.js b/tests/unit/core/selector.js index 11490794a..e7b7f9b6c 100644 --- a/tests/unit/core/selector.js +++ b/tests/unit/core/selector.js @@ -79,7 +79,7 @@ test("data", function() { }); test("focusable - visible, enabled elements", function() { - expect(18); + expect(16); isFocusable('#visibleAncestor-inputTypeNone', 'input, no type'); isFocusable('#visibleAncestor-inputTypeText', 'input, type text'); @@ -93,9 +93,6 @@ test("focusable - visible, enabled elements", function() { isFocusable('#visibleAncestor-object', 'object'); isFocusable('#visibleAncestor-anchorWithHref', 'anchor with href'); isNotFocusable('#visibleAncestor-anchorWithoutHref', 'anchor without href'); - // fails: $("map").is(":visible") and $("map").is(":hidden") both return true - isFocusable('#visibleAncestor-areaWithHref', 'area with href'); - isNotFocusable('#visibleAncestor-areaWithoutHref', 'area without href'); isNotFocusable('#visibleAncestor-span', 'span'); isNotFocusable('#visibleAncestor-div', 'div'); isFocusable("#visibleAncestor-spanWithTabindex", 'span with tabindex'); @@ -159,8 +156,16 @@ test("focusable - invalid tabindex", function() { isNotFocusable('#spanTabindex3foo', 'span, tabindex 3foo'); }); +test("focusable - area elements", function() { + isNotFocusable('#areaCoordsNoHref', 'coords but no href'); + isFocusable('#areaCoordsHref', 'coords and href'); + isFocusable('#areaCoordsNoSizeHref', 'coords of zero px and href'); + isFocusable('#areaNoCoordsHref', 'href but no coords'); + isNotFocusable('#areaNoImg', 'not associated with an image'); +}); + test("tabbable - visible, enabled elements", function() { - expect(18); + expect(16); isTabbable('#visibleAncestor-inputTypeNone', 'input, no type'); isTabbable('#visibleAncestor-inputTypeText', 'input, type text'); @@ -174,16 +179,13 @@ test("tabbable - visible, enabled elements", function() { isTabbable('#visibleAncestor-object', 'object'); isTabbable('#visibleAncestor-anchorWithHref', 'anchor with href'); isNotTabbable('#visibleAncestor-anchorWithoutHref', 'anchor without href'); - // fails: $("map").is(":visible") and $("map").is(":hidden") both return true - isTabbable('#visibleAncestor-areaWithHref', 'area with href'); - isNotTabbable('#visibleAncestor-areaWithoutHref', 'area without href'); isNotTabbable('#visibleAncestor-span', 'span'); isNotTabbable('#visibleAncestor-div', 'div'); isTabbable("#visibleAncestor-spanWithTabindex", 'span with tabindex'); isNotTabbable("#visibleAncestor-divWithNegativeTabindex", 'div with tabindex'); }); -test("Tabbable - disabled elements", function() { +test("tabbable - disabled elements", function() { expect(9); isNotTabbable('#disabledElement-inputTypeNone', 'input, no type'); @@ -197,25 +199,23 @@ test("Tabbable - disabled elements", function() { isNotTabbable('#disabledElement-textarea', 'textarea'); }); -test("Tabbable - hidden styles", function() { +test("tabbable - hidden styles", function() { expect(8); isNotTabbable('#displayNoneAncestor-input', 'input, display: none parent'); isNotTabbable('#displayNoneAncestor-span', 'span with tabindex, display: none parent'); - // fails: element hidden by parent-visibility-hidden is still visible according to :visible isNotTabbable('#visibilityHiddenAncestor-input', 'input, visibility: hidden parent'); isNotTabbable('#visibilityHiddenAncestor-span', 'span with tabindex, visibility: hidden parent'); isNotTabbable('#displayNone-input', 'input, display: none'); - // fails: element hidden by parent-visibility-hidden is still visible according to :visible isNotTabbable('#visibilityHidden-input', 'input, visibility: hidden'); isNotTabbable('#displayNone-span', 'span with tabindex, display: none'); isNotTabbable('#visibilityHidden-span', 'span with tabindex, visibility: hidden'); }); -test("Tabbable - natively tabbable with various tabindex", function() { +test("tabbable - natively tabbable with various tabindex", function() { expect(4); isTabbable('#inputTabindex0', 'input, tabindex 0'); @@ -224,7 +224,7 @@ test("Tabbable - natively tabbable with various tabindex", function() { isNotTabbable('#inputTabindex-50', 'input, tabindex -50'); }); -test("Tabbable - not natively tabbable with various tabindex", function() { +test("tabbable - not natively tabbable with various tabindex", function() { expect(4); isTabbable('#spanTabindex0', 'span, tabindex 0'); @@ -233,7 +233,7 @@ test("Tabbable - not natively tabbable with various tabindex", function() { isNotTabbable('#spanTabindex-50', 'span, tabindex -50'); }); -test("Tabbable - invalid tabindex", function() { +test("tabbable - invalid tabindex", function() { expect(4); isTabbable('#inputTabindexfoo', 'input, tabindex foo'); @@ -242,4 +242,12 @@ test("Tabbable - invalid tabindex", function() { isNotTabbable('#spanTabindex3foo', 'span, tabindex 3foo'); }); +test("tabbable - area elements", function() { + isNotTabbable('#areaCoordsNoHref', 'coords but no href'); + isTabbable('#areaCoordsHref', 'coords and href'); + isTabbable('#areaCoordsNoSizeHref', 'coords of zero px and href'); + isTabbable('#areaNoCoordsHref', 'href but no coords'); + isNotTabbable('#areaNoImg', 'not associated with an image'); +}); + })(jQuery); diff --git a/ui/jquery.ui.core.js b/ui/jquery.ui.core.js index 0804edab7..49b5c003b 100644 --- a/ui/jquery.ui.core.js +++ b/ui/jquery.ui.core.js @@ -231,6 +231,13 @@ $.each( [ "Width", "Height" ], function( i, name ) { }); //Additional selectors +function visible( element ) { + return !$(element).parents().andSelf().filter(function() { + return $.curCSS( this, "visibility" ) === "hidden" || + $.expr.filters.hidden( this ); + }).length; +} + $.extend($.expr[':'], { data: function(elem, i, match) { return !!$.data(elem, match[3]); @@ -239,17 +246,23 @@ $.extend($.expr[':'], { focusable: function(element) { var nodeName = element.nodeName.toLowerCase(), tabIndex = $.attr(element, 'tabindex'); + if ( "area" === nodeName ) { + var map = element.parentNode, + mapName = map.name, + img; + 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 || 'area' == nodeName + : 'a' == nodeName ? element.href || !isNaN(tabIndex) : !isNaN(tabIndex)) // the element and all of its ancestors must be visible - // the browser may report that the area is hidden - && !$(element).parents().andSelf().filter(function() { - return $.curCSS( this, "visibility" ) === "hidden" || - $.expr.filters.hidden( this ); - }).length; + && visible( element ); }, tabbable: function(element) {