diff options
author | Richard Gibson <richard.gibson@gmail.com> | 2016-01-11 02:26:55 -0500 |
---|---|---|
committer | Timmy Willison <timmywillisn@gmail.com> | 2016-01-13 16:05:09 -0500 |
commit | dba93f79c405373ec3a492fd0a4bf89b3136a6e6 (patch) | |
tree | 49aca78a21bb34ebb7d834a270c090b9de91522a | |
parent | a268f5225cad9ab380494e61a10105cc9eb107e7 (diff) | |
download | jquery-dba93f79c405373ec3a492fd0a4bf89b3136a6e6.tar.gz jquery-dba93f79c405373ec3a492fd0a4bf89b3136a6e6.zip |
CSS: Restore cascade-override behavior in .show
Fixes gh-2654
Fixes gh-2308
Close gh-2810
Ref 86419b10bfa5e3b71a7d416288ab806d47a31d1f
-rw-r--r-- | src/css/showHide.js | 44 | ||||
-rw-r--r-- | src/effects.js | 13 | ||||
-rw-r--r-- | test/data/testsuite.css | 14 | ||||
-rw-r--r-- | test/unit/css.js | 275 | ||||
-rw-r--r-- | test/unit/effects.js | 161 |
5 files changed, 391 insertions, 116 deletions
diff --git a/src/css/showHide.js b/src/css/showHide.js index 29e2d8bc8..9c62d5564 100644 --- a/src/css/showHide.js +++ b/src/css/showHide.js @@ -4,6 +4,31 @@ define( [ "../css/var/isHidden" ], function( jQuery, dataPriv, isHidden ) { +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ), + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + function showHide( elements, show ) { var display, elem, values = [], @@ -19,23 +44,30 @@ function showHide( elements, show ) { display = elem.style.display; if ( show ) { - if ( display === "none" ) { - // Restore a pre-hide() value if we have one - values[ index ] = dataPriv.get( elem, "display" ) || ""; + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && jQuery.css( elem, "display" ) === "none" ) { + values[ index ] = getDefaultDisplay( elem ); } } else { if ( display !== "none" ) { values[ index ] = "none"; - // Remember the value we're replacing + // Remember what we're overwriting dataPriv.set( elem, "display", display ); } } } - // Set the display of the elements in a second loop - // to avoid the constant reflow + // Set the display of the elements in a second loop to avoid constant reflow for ( index = 0; index < length; index++ ) { if ( values[ index ] != null ) { elements[ index ].style.display = values[ index ]; diff --git a/src/effects.js b/src/effects.js index 64f9353ef..43162e1fb 100644 --- a/src/effects.js +++ b/src/effects.js @@ -154,9 +154,16 @@ function defaultPrefilter( elem, props, opts ) { } display = jQuery.css( elem, "display" ); if ( display === "none" ) { - display = restoreDisplay || swap( elem, { "display": "" }, function() { - return jQuery.css( elem, "display" ); - } ); + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } } // Animate inline elements as inline-block diff --git a/test/data/testsuite.css b/test/data/testsuite.css index 50619b98d..253bea998 100644 --- a/test/data/testsuite.css +++ b/test/data/testsuite.css @@ -20,10 +20,6 @@ div#fx-tests div.overflow { overflow: visible; } -div.inline { - display: inline; -} - div.autoheight { height: auto; } @@ -68,11 +64,6 @@ div.noopacity { opacity: 0; } -div.hidden, -span.hidden { - display: none; -} - div#fx-tests div.widewidth { background-repeat: repeat-x; } @@ -134,3 +125,8 @@ section { background:#f0f; display:block; } #span-14824 { display: block; } #display { display: list-item !important; } + +.block { display: block; } +.inline { display: inline; } +.list-item { display: list-item; } +.hidden, .none { display: none; } diff --git a/test/unit/css.js b/test/unit/css.js index 4b4017ab8..4ecd08a68 100644 --- a/test/unit/css.js +++ b/test/unit/css.js @@ -474,26 +474,7 @@ QUnit.test( "css(Object) where values are Functions with incoming values", funct // .show(), .hide(), can be excluded from the build if ( jQuery.fn.show && jQuery.fn.hide ) { -QUnit.test( "show(); hide()", function( assert ) { - - assert.expect( 4 ); - - var hiddendiv, div; - - hiddendiv = jQuery( "div.hidden" ); - hiddendiv.hide(); - assert.equal( hiddendiv.css( "display" ), "none", "Cascade-hidden div after hide()" ); - hiddendiv.show(); - assert.equal( hiddendiv.css( "display" ), "none", "Show does not trump CSS cascade" ); - - div = jQuery( "<div>" ).hide(); - assert.equal( div.css( "display" ), "none", "Detached div hidden" ); - div.appendTo( "#qunit-fixture" ).show(); - assert.equal( div.css( "display" ), "block", "Initially-detached div after show()" ); - -} ); - -QUnit.test( "show();", function( assert ) { +QUnit.test( "show()", function( assert ) { assert.expect( 18 ); @@ -557,11 +538,20 @@ QUnit.test( "show();", function( assert ) { jQuery( "<div>test</div> text <span>test</span>" ).hide().remove(); } ); -QUnit.test( "show() resolves correct default display for detached nodes", function( assert ) { - assert.expect( 16 ); +QUnit.test( "show/hide detached nodes", function( assert ) { + assert.expect( 19 ); var div, span, tr; + div = jQuery( "<div>" ).hide(); + assert.equal( div.css( "display" ), "none", "hide() updates inline style of a detached div" ); + div.appendTo( "#qunit-fixture" ); + assert.equal( div.css( "display" ), "none", + "A hidden-while-detached div is hidden after attachment" ); + div.show(); + assert.equal( div.css( "display" ), "block", + "A hidden-while-detached div can be shown after attachment" ); + div = jQuery( "<div class='hidden'>" ); div.show().appendTo( "#qunit-fixture" ); assert.equal( div.css( "display" ), "none", @@ -664,6 +654,247 @@ QUnit.test( "show() after hide() should always set display to initial value (#14 assert.equal( div.css( "display" ), "list-item", "should get last set display value" ); } ); +QUnit.test( "show/hide 3.0, default display", function( assert ) { + + assert.expect( 36 ); + + var i, + $elems = jQuery( "<div/>" ) + .appendTo( "#qunit-fixture" ) + .html( "<div data-expected-display='block'/>" + + "<span data-expected-display='inline'/>" + + "<ul><li data-expected-display='list-item'/></ul>" ) + .find( "[data-expected-display]" ); + + $elems.each( function() { + var $elem = jQuery( this ), + name = this.nodeName, + expected = this.getAttribute( "data-expected-display" ), + sequence = []; + + if ( this.className ) { + name += "." + this.className; + } + if ( this.getAttribute( "style" ) ) { + name += "[style='" + this.getAttribute( "style" ) + "']"; + } + name += " "; + + for ( i = 0; i < 3; i++ ) { + sequence.push( ".show()" ); + $elem.show(); + assert.equal( $elem.css( "display" ), expected, + name + sequence.join( "" ) + " computed" ); + assert.equal( this.style.display, "", name + sequence.join( "" ) + " inline" ); + + sequence.push( ".hide()" ); + $elem.hide(); + assert.equal( $elem.css( "display" ), "none", + name + sequence.join( "" ) + " computed" ); + assert.equal( this.style.display, "none", name + sequence.join( "" ) + " inline" ); + } + } ); +} ); + +QUnit.test( "show/hide 3.0, default body display", function( assert ) { + + assert.expect( 2 ); + + var hideBody = supportjQuery( "<style>body{display:none}</style>" ).appendTo( document.head ), + body = jQuery( document.body ); + + assert.equal( body.css( "display" ), "none", "Correct initial display" ); + + body.show(); + + assert.equal( body.css( "display" ), "block", "Correct display after .show()" ); + + hideBody.remove(); +} ); + +QUnit.test( "show/hide 3.0, cascade display", function( assert ) { + + assert.expect( 36 ); + + var i, + $elems = jQuery( "<div/>" ) + .appendTo( "#qunit-fixture" ) + .html( "<span class='block'/><div class='inline'/><div class='list-item'/>" ) + .children(); + + $elems.each( function() { + var $elem = jQuery( this ), + name = this.nodeName, + sequence = []; + + if ( this.className ) { + name += "." + this.className; + } + if ( this.getAttribute( "style" ) ) { + name += "[style='" + this.getAttribute( "style" ) + "']"; + } + name += " "; + + for ( i = 0; i < 3; i++ ) { + sequence.push( ".show()" ); + $elem.show(); + assert.equal( $elem.css( "display" ), this.className, + name + sequence.join( "" ) + " computed" ); + assert.equal( this.style.display, "", name + sequence.join( "" ) + " inline" ); + + sequence.push( ".hide()" ); + $elem.hide(); + assert.equal( $elem.css( "display" ), "none", + name + sequence.join( "" ) + " computed" ); + assert.equal( this.style.display, "none", name + sequence.join( "" ) + " inline" ); + } + } ); +} ); + +QUnit.test( "show/hide 3.0, inline display", function( assert ) { + + assert.expect( 96 ); + + var i, + $elems = jQuery( "<div/>" ) + .appendTo( "#qunit-fixture" ) + .html( "<span data-expected-display='block' style='display:block'/>" + + "<span class='list-item' data-expected-display='block' style='display:block'/>" + + "<div data-expected-display='inline' style='display:inline'/>" + + "<div class='list-item' data-expected-display='inline' style='display:inline'/>" + + "<ul>" + + "<li data-expected-display='block' style='display:block'/>" + + "<li class='inline' data-expected-display='block' style='display:block'/>" + + "<li data-expected-display='inline' style='display:inline'/>" + + "<li class='block' data-expected-display='inline' style='display:inline'/>" + + "</ul>" ) + .find( "[data-expected-display]" ); + + $elems.each( function() { + var $elem = jQuery( this ), + name = this.nodeName, + expected = this.getAttribute( "data-expected-display" ), + sequence = []; + + if ( this.className ) { + name += "." + this.className; + } + if ( this.getAttribute( "style" ) ) { + name += "[style='" + this.getAttribute( "style" ) + "']"; + } + name += " "; + + for ( i = 0; i < 3; i++ ) { + sequence.push( ".show()" ); + $elem.show(); + assert.equal( $elem.css( "display" ), expected, + name + sequence.join( "" ) + " computed" ); + assert.equal( this.style.display, expected, name + sequence.join( "" ) + " inline" ); + + sequence.push( ".hide()" ); + $elem.hide(); + assert.equal( $elem.css( "display" ), "none", + name + sequence.join( "" ) + " computed" ); + assert.equal( this.style.display, "none", name + sequence.join( "" ) + " inline" ); + } + } ); +} ); + +QUnit.test( "show/hide 3.0, cascade hidden", function( assert ) { + + assert.expect( 72 ); + + var i, + $elems = jQuery( "<div/>" ) + .appendTo( "#qunit-fixture" ) + .html( "<div class='hidden' data-expected-display='block'/>" + + "<div class='hidden' data-expected-display='block' style='display:none'/>" + + "<span class='hidden' data-expected-display='inline'/>" + + "<span class='hidden' data-expected-display='inline' style='display:none'/>" + + "<ul>" + + "<li class='hidden' data-expected-display='list-item'/>" + + "<li class='hidden' data-expected-display='list-item' style='display:none'/>" + + "</ul>" ) + .find( "[data-expected-display]" ); + + $elems.each( function() { + var $elem = jQuery( this ), + name = this.nodeName, + expected = this.getAttribute( "data-expected-display" ), + sequence = []; + + if ( this.className ) { + name += "." + this.className; + } + if ( this.getAttribute( "style" ) ) { + name += "[style='" + this.getAttribute( "style" ) + "']"; + } + name += " "; + + for ( i = 0; i < 3; i++ ) { + sequence.push( ".hide()" ); + $elem.hide(); + assert.equal( $elem.css( "display" ), "none", + name + sequence.join( "" ) + " computed" ); + assert.equal( this.style.display, "none", name + sequence.join( "" ) + " inline" ); + + sequence.push( ".show()" ); + $elem.show(); + assert.equal( $elem.css( "display" ), expected, + name + sequence.join( "" ) + " computed" ); + assert.equal( this.style.display, expected, name + sequence.join( "" ) + " inline" ); + } + } ); +} ); + +QUnit.test( "show/hide 3.0, inline hidden", function( assert ) { + + assert.expect( 84 ); + + var i, + $elems = jQuery( "<div/>" ) + .appendTo( "#qunit-fixture" ) + .html( "<span data-expected-display='inline' style='display:none'/>" + + "<span class='list-item' data-expected-display='list-item' style='display:none'/>" + + "<div data-expected-display='block' style='display:none'/>" + + "<div class='list-item' data-expected-display='list-item' style='display:none'/>" + + "<ul>" + + "<li data-expected-display='list-item' style='display:none'/>" + + "<li class='block' data-expected-display='block' style='display:none'/>" + + "<li class='inline' data-expected-display='inline' style='display:none'/>" + + "</ul>" ) + .find( "[data-expected-display]" ); + + $elems.each( function() { + var $elem = jQuery( this ), + name = this.nodeName, + expected = this.getAttribute( "data-expected-display" ), + sequence = []; + + if ( this.className ) { + name += "." + this.className; + } + if ( this.getAttribute( "style" ) ) { + name += "[style='" + this.getAttribute( "style" ) + "']"; + } + name += " "; + + for ( i = 0; i < 3; i++ ) { + sequence.push( ".hide()" ); + $elem.hide(); + assert.equal( $elem.css( "display" ), "none", + name + sequence.join( "" ) + " computed" ); + assert.equal( this.style.display, "none", name + sequence.join( "" ) + " inline" ); + + sequence.push( ".show()" ); + $elem.show(); + assert.equal( $elem.css( "display" ), expected, + name + sequence.join( "" ) + " computed" ); + assert.equal( this.style.display, "", name + sequence.join( "" ) + " inline" ); + } + } ); +} ); + } QUnit[ jQuery.find.compile && jQuery.fn.toggle ? "test" : "skip" ]( "toggle()", function( assert ) { diff --git a/test/unit/effects.js b/test/unit/effects.js index 33357deda..ae43674bd 100644 --- a/test/unit/effects.js +++ b/test/unit/effects.js @@ -5,7 +5,11 @@ if ( !jQuery.fx ) { return; } -var oldRaf = window.requestAnimationFrame; +var oldRaf = window.requestAnimationFrame, + hideOptions = { + inline: function() { jQuery.style( this, "display", "none" ); }, + cascade: function() { this.className = "hidden"; } + }; QUnit.module( "effects", { setup: function() { @@ -131,98 +135,103 @@ QUnit.test( "show()", function( assert ) { jQuery( "<div>test</div> text <span>test</span>" ).hide().remove(); } ); -QUnit.test( "show(Number) - other displays", function( assert ) { - assert.expect( 30 ); - - jQuery( - "<div id='show-tests'>" + - "<div><p><a href='#'></a></p><code></code><pre></pre><span></span></div>" + - "<table><thead><tr><th></th></tr></thead><tbody><tr><td></td></tr></tbody></table>" + - "<ul><li></li></ul></div>" + - "<table id='test-table'></table>" - ).appendTo( "#qunit-fixture" ).find( "*" ).css( "display", "none" ); - - var test, - old = jQuery( "#test-table" ).show().css( "display" ) !== "table"; - - jQuery( "#test-table" ).remove(); - - // Note: inline elements are expected to be inline-block - // because we're showing width/height - // Can't animate width/height inline - // See #14344 - test = { - "div": "block", - "p": "block", - "a": "inline", - "code": "inline", - "pre": "block", - "span": "inline", - "table": old ? "block" : "table", - "thead": old ? "block" : "table-header-group", - "tbody": old ? "block" : "table-row-group", - "tr": old ? "block" : "table-row", - "th": old ? "block" : "table-cell", - "td": old ? "block" : "table-cell", - "ul": "block", - "li": old ? "block" : "list-item" - }; +supportjQuery.each( hideOptions, function( type, setup ) { + QUnit.test( "show(Number) - " + type + " hidden", function( assert ) { + assert.expect( 30 ); + + jQuery( + "<div id='show-tests'>" + + "<div><p><a href='#'></a></p><code></code><pre></pre><span></span></div>" + + "<table><thead><tr><th></th></tr></thead><tbody><tr><td></td></tr></tbody>" + + "</table>" + + "<ul><li></li></ul></div>" + + "<table id='test-table'></table>" + ).appendTo( "#qunit-fixture" ).find( "*" ).each( setup ); + + var test, + old = jQuery( "#test-table" ).show().css( "display" ) !== "table"; + + jQuery( "#test-table" ).remove(); + + // Note: inline elements are expected to be inline-block + // because we're showing width/height + // Can't animate width/height inline + // See #14344 + test = { + "div": "block", + "p": "block", + "a": "inline", + "code": "inline", + "pre": "block", + "span": "inline", + "table": old ? "block" : "table", + "thead": old ? "block" : "table-header-group", + "tbody": old ? "block" : "table-row-group", + "tr": old ? "block" : "table-row", + "th": old ? "block" : "table-cell", + "td": old ? "block" : "table-cell", + "ul": "block", + "li": old ? "block" : "list-item" + }; - jQuery.each( test, function( selector ) { - jQuery( selector, "#show-tests" ).show( 100 ); - } ); - this.clock.tick( 50 ); - jQuery.each( test, function( selector, expected ) { - jQuery( selector, "#show-tests" ).each( function() { - assert.equal( - jQuery( this ).css( "display" ), - expected === "inline" ? "inline-block" : expected, - "Correct display type during animation for " + selector - ); + jQuery.each( test, function( selector ) { + jQuery( selector, "#show-tests" ).show( 100 ); } ); - } ); - this.clock.tick( 50 ); - jQuery.each( test, function( selector, expected ) { - jQuery( selector, "#show-tests" ).each( function() { - assert.equal( jQuery( this ).css( "display" ), expected, - "Correct display type after animation for " + selector ); + this.clock.tick( 50 ); + jQuery.each( test, function( selector, expected ) { + jQuery( selector, "#show-tests" ).each( function() { + assert.equal( + jQuery( this ).css( "display" ), + expected === "inline" ? "inline-block" : expected, + "Correct display type during animation for " + selector + ); + } ); + } ); + this.clock.tick( 50 ); + jQuery.each( test, function( selector, expected ) { + jQuery( selector, "#show-tests" ).each( function() { + assert.equal( jQuery( this ).css( "display" ), expected, + "Correct display type after animation for " + selector ); + } ); } ); - } ); - jQuery( "#show-tests" ).remove(); + jQuery( "#show-tests" ).remove(); + } ); } ); // Supports #7397 -QUnit.test( "Persist correct display value", function( assert ) { - assert.expect( 3 ); +supportjQuery.each( hideOptions, function( type, setup ) { + QUnit.test( "Persist correct display value - " + type + " hidden", function( assert ) { + assert.expect( 3 ); - jQuery( "<div id='show-tests'><span style='position:absolute;'>foo</span></div>" ) - .appendTo( "#qunit-fixture" ).find( "*" ).css( "display", "none" ); + jQuery( "<div id='show-tests'><span style='position:absolute;'>foo</span></div>" ) + .appendTo( "#qunit-fixture" ).find( "*" ).each( setup ); - var $span = jQuery( "#show-tests span" ), - displayNone = $span.css( "display" ), - display = "", - clock = this.clock; + var $span = jQuery( "#show-tests span" ), + displayNone = $span.css( "display" ), + display = "", + clock = this.clock; - $span.show(); + $span.show(); - display = $span.css( "display" ); + display = $span.css( "display" ); - $span.hide(); + $span.hide(); - $span.fadeIn( 100, function() { - assert.equal( $span.css( "display" ), display, "Expecting display: " + display ); - $span.fadeOut( 100, function() { - assert.equal( $span.css( "display" ), displayNone, "Expecting display: " + displayNone ); - $span.fadeIn( 100, function() { - assert.equal( $span.css( "display" ), display, "Expecting display: " + display ); + $span.fadeIn( 100, function() { + assert.equal( $span.css( "display" ), display, "Expecting display: " + display ); + $span.fadeOut( 100, function() { + assert.equal( $span.css( "display" ), displayNone, "Expecting display: " + displayNone ); + $span.fadeIn( 100, function() { + assert.equal( $span.css( "display" ), display, "Expecting display: " + display ); + } ); } ); } ); - } ); - clock.tick( 300 ); + clock.tick( 300 ); - assert.expectJqData( this, $span, "olddisplay" ); + assert.expectJqData( this, $span, "olddisplay" ); + } ); } ); QUnit.test( "animate(Hash, Object, Function)", function( assert ) { |