From 619bf98d5b479f9582dbc40259b666f1c5a83146 Mon Sep 17 00:00:00 2001 From: Connor Atherton Date: Sat, 25 Jun 2016 10:21:00 -0700 Subject: [PATCH] CSS: Support custom properties Fixes gh-3144 Closes gh-3199 Closes gh-3557 --- src/css.js | 39 +++++++++++++++++++++++------ src/css/curCSS.js | 5 ++-- test/unit/css.js | 64 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 9 deletions(-) diff --git a/src/css.js b/src/css.js index c17265ced..7a225d9a9 100644 --- a/src/css.js +++ b/src/css.js @@ -28,6 +28,7 @@ var // except "table", "table-cell", or "table-caption" // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, cssShow = { position: "absolute", visibility: "hidden", display: "block" }, cssNormalTransform = { letterSpacing: "0", @@ -57,6 +58,16 @@ function vendorPropName( name ) { } } +// Return a property mapped along what jQuery.cssProps suggests or to +// a vendor prefixed property. +function finalPropName( name ) { + var ret = jQuery.cssProps[ name ]; + if ( !ret ) { + ret = jQuery.cssProps[ name ] = vendorPropName( name ) || name; + } + return ret; +} + function setPositiveNumber( elem, value, subtract ) { // Any relative (+/-) values have already been @@ -218,10 +229,15 @@ jQuery.extend( { // Make sure that we're working with the right name var ret, type, hooks, origName = jQuery.camelCase( name ), + isCustomProp = rcustomProp.test( name ), style = elem.style; - name = jQuery.cssProps[ origName ] || - ( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName ); + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } // Gets hook for the prefixed version, then unprefixed version hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; @@ -257,7 +273,11 @@ jQuery.extend( { if ( !hooks || !( "set" in hooks ) || ( value = hooks.set( elem, value, extra ) ) !== undefined ) { - style[ name ] = value; + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } } } else { @@ -276,11 +296,15 @@ jQuery.extend( { css: function( elem, name, extra, styles ) { var val, num, hooks, - origName = jQuery.camelCase( name ); + origName = jQuery.camelCase( name ), + isCustomProp = rcustomProp.test( name ); - // Make sure that we're working with the right name - name = jQuery.cssProps[ origName ] || - ( jQuery.cssProps[ origName ] = vendorPropName( origName ) || origName ); + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } // Try prefixed name followed by the unprefixed name hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; @@ -305,6 +329,7 @@ jQuery.extend( { num = parseFloat( val ); return extra === true || isFinite( num ) ? num || 0 : val; } + return val; } } ); diff --git a/src/css/curCSS.js b/src/css/curCSS.js index 313da4222..de5333030 100644 --- a/src/css/curCSS.js +++ b/src/css/curCSS.js @@ -15,8 +15,9 @@ function curCSS( elem, name, computed ) { computed = computed || getStyles( elem ); - // Support: IE <=9 only - // getPropertyValue is only needed for .css('filter') (#12537) + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) if ( computed ) { ret = computed.getPropertyValue( name ) || computed[ name ]; diff --git a/test/unit/css.js b/test/unit/css.js index 2f529b691..460023b62 100644 --- a/test/unit/css.js +++ b/test/unit/css.js @@ -1556,4 +1556,68 @@ QUnit.test( "Do not throw on frame elements from css method (#15098)", function( } )(); + +QUnit.test( "css(--customProperty)", function( assert ) { + jQuery( "#qunit-fixture" ).append( + "" + ); + + var div = jQuery( "
" ).appendTo( "#qunit-fixture" ), + $elem = jQuery( "
" ).addClass( "test__customProperties" ).appendTo( "#qunit-fixture" ), + webkit = /\bsafari\b/i.test( navigator.userAgent ) && + !/\firefox\b/i.test( navigator.userAgent ) && + !/\edge\b/i.test( navigator.userAgent ), + oldSafari = webkit && ( /\b9\.\d(\.\d+)* safari/i.test( navigator.userAgent ) || + /\b10\.0(\.\d+)* safari/i.test( navigator.userAgent ) ), + expected = 10; + + if ( webkit ) { + expected -= 2; + } + if ( oldSafari ) { + expected -= 2; + } + assert.expect( expected ); + + div.css( "--color", "blue" ); + assert.equal( div.css( "--color" ), "blue", "Modified CSS custom property using string" ); + + div.css( "--color", "yellow" ); + assert.equal( div.css( "--color" ), "yellow", "Overwrite CSS custom property" ); + + div.css( { "--color": "red" } ); + assert.equal( div.css( "--color" ), "red", "Modified CSS custom property using object" ); + + div.css( { "--mixedCase": "green" } ); + assert.equal( div.css( "--mixedCase" ), "green", "Modified CSS custom property with mixed case" ); + + div.css( { "--theme-dark": "purple" } ); + assert.equal( div.css( "--theme-dark" ), "purple", "Modified CSS custom property with dashed name" ); + + assert.equal( $elem.css( "--prop1" ), "val1", "Basic CSS custom property" ); + + // Support: Safari 9.1-10.0 only + // Safari collapses whitespaces & quotes. Ignore it. + if ( !oldSafari ) { + assert.equal( $elem.css( "--prop2" ), " val2", "Preceding whitespace maintained" ); + assert.equal( $elem.css( "--prop3" ), "val3 ", "Following whitespace maintained" ); + } + + // Support: Chrome 49-55, Safari 9.1-10.0 + // Chrome treats single quotes as double ones. + // Safari treats double quotes as single ones. + if ( !webkit ) { + assert.equal( $elem.css( "--prop4" ), "\"val4\"", "Works with double quotes" ); + assert.equal( $elem.css( "--prop5" ), "'val5'", "Works with single quotes" ); + } +} ); + }