]> source.dussan.org Git - jquery.git/commitdiff
CSS: Support custom properties 3557/head
authorConnor Atherton <c.liam.atherton@gmail.com>
Sat, 25 Jun 2016 17:21:00 +0000 (10:21 -0700)
committerMichał Gołębiowski <m.goleb@gmail.com>
Tue, 7 Mar 2017 13:52:08 +0000 (14:52 +0100)
Fixes gh-3144
Closes gh-3199
Closes gh-3557

src/css.js
src/css/curCSS.js
test/unit/css.js

index c17265ced5547217ad079b0bee8b2f977c8bb3ba..7a225d9a9ac69613407a3ed2f4832ecd97f4a84d 100644 (file)
@@ -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;
        }
 } );
index 313da422215113d4eaf267f7b0ff1b85e4a8a86f..de5333030278e87e88f0084d4d600823722b05db 100644 (file)
@@ -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 ];
 
index 2f529b691139fb8ce742d26cbbe081213762fdf7..460023b62715812f82bdac2681b526961e0980b2 100644 (file)
@@ -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(
+               "<style>\n" +
+               "    .test__customProperties {\n" +
+               "        --prop1:val1;\n" +
+               "        --prop2: val2;\n" +
+               "        --prop3:val3 ;\n" +
+               "        --prop4:\"val4\";\n" +
+               "        --prop5:'val5';\n" +
+               "    }\n" +
+               "</style>"
+       );
+
+       var div = jQuery( "<div>" ).appendTo( "#qunit-fixture" ),
+               $elem = jQuery( "<div>" ).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" );
+       }
+} );
+
 }