Repository: mui/material-ui (TypeScript)
Date: 2020-05-06 14:31:28+00:00
Task type: Bug Fix

Issue:
CSS4: hexToRgb does not support #rrggbbaa pattern causes DOM crash
- [X] The issue is present in the latest release.
- [X] I have searched the [issues](https://github.com/mui-org/material-ui/issues) of this repository and believe that this is not a duplicate.


Recently I got bitten by this DOM error:

```
Error: Material-UI: unsupported `` color.
We support the following formats: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla().
```

It caused by this node:

```jsx
 <SpeedDialAction
       icon={<CallIcon/>}
       tooltipTitle="Call me"
       onClick={() => [setOpen(false)]}
   />
```

After deep digging turns out it caused by

https://github.com/mui-org/material-ui/blob/54ace6528ed4df4798c9a91e6e5f42e4dc29ccaa/packages/material-ui/src/styles/colorManipulator.js#L109-L114

The `type` is an empty string from:

https://github.com/mui-org/material-ui/blob/54ace6528ed4df4798c9a91e6e5f42e4dc29ccaa/packages/material-ui/src/styles/colorManipulator.js#L27

That function returns empty string with `#rrggbbaa`. Found out that it comes from:

https://github.com/mui-org/material-ui/blob/54ace6528ed4df4798c9a91e6e5f42e4dc29ccaa/packages/material-ui-lab/src/SpeedDialAction/SpeedDialAction.js#L18

which of course crashing my app because in my project setup I'm used this setting:

```
createMuiTheme({
    palette: {
      background: {
        paper: '#111111f8',
      }
   }
})
```

I choose to workaround it by using `rgba()`. Indeed it does works. 


Issue discussion:
Thanks for the report. The overhead of supporting this format seems low, I think that we could do it. What do you think of this diff? Do you want to work on a pull request? :)

```diff
diff --git a/packages/material-ui/src/styles/colorManipulator.js b/packages/material-ui/src/styles/colorManipulator.js
index 8ef994f7d..ad8abf1cc 100644
--- a/packages/material-ui/src/styles/colorManipulator.js
+++ b/packages/material-ui/src/styles/colorManipulator.js
@@ -27,14 +27,17 @@ function clamp(value, min = 0, max = 1) {
 export function hexToRgb(color) {
   color = color.substr(1);

-  const re = new RegExp(`.{1,${color.length / 3}}`, 'g');
+  const re = new RegExp(`.{1,${Math.min(color.length / 3, 2)}}`, 'g');
   let colors = color.match(re);

+  // Expand shorthand notation
   if (colors && colors[0].length === 1) {
     colors = colors.map((n) => n + n);
   }

-  return colors ? `rgb(${colors.map((n) => parseInt(n, 16)).join(', ')})` : '';
+  return colors
+    ? `rgb${colors.length === 4 ? 'a' : ''}(${colors.map((n) => parseInt(n, 16)).join(', ')})`
+    : '';
 }

 function intToHex(int) {
diff --git a/packages/material-ui/src/styles/colorManipulator.test.js b/packages/material-ui/src/styles/colorManipulator.test.js
index ecab48a1f..03bc3465c 100644
--- a/packages/material-ui/src/styles/colorManipulator.test.js
+++ b/packages/material-ui/src/styles/colorManipulator.test.js
@@ -1,4 +1,4 @@
-import { assert } from 'chai';
+import { assert, expect } from 'chai';
 import consoleErrorMock from 'test/utils/consoleErrorMock';
 import {
   recomposeColor,
@@ -129,6 +129,14 @@ describe('utils/colorManipulator', () => {
       const output2 = decomposeColor(output1);
       assert.strictEqual(output1, output2);
     });
+
+    it('convert rgba hex', () => {
+      const decomposed = decomposeColor('#111111f8');
+      expect(decomposed).to.deep.equal({
+        type: 'rgba',
+        values: [17, 17, 17, 248],
+      });
+    });
   });

   describe('getContrastRatio', () => {
```

Hum, the last index seems wrong, it should be between [0,1], not [0, 255].
It seems that there is more to it, with a four digits approach https://drafts.csswg.org/css-color/#hex-notation. Be careful with the browser support https://caniuse.com/#feat=css-rrggbbaa.
@willnode are you working on this issue?

@ShehryarShoukat96 If you do decide to work on this problem, also have #20492 in mind (while they should be solved into different pull requests, it will likely influence the design decisions). At this stage, my only concern would be with the bundle size vs how frequent these features are needed tradeoff.
@ShehryarShoukat96 I haven't working on it yet because of daily work. Sorry. I might have chance to do it later during weekend (probably). You're welcome to fix this problem if you decide anyway 👍 

Solution patch:
diff --git a/packages/material-ui/src/styles/colorManipulator.js b/packages/material-ui/src/styles/colorManipulator.js
--- a/packages/material-ui/src/styles/colorManipulator.js
+++ b/packages/material-ui/src/styles/colorManipulator.js
@@ -27,14 +27,20 @@ function clamp(value, min = 0, max = 1) {
 export function hexToRgb(color) {
   color = color.substr(1);
 
-  const re = new RegExp(`.{1,${color.length / 3}}`, 'g');
+  const re = new RegExp(`.{1,${color.length >= 6 ? 2 : 1}}`, 'g');
   let colors = color.match(re);
 
   if (colors && colors[0].length === 1) {
     colors = colors.map((n) => n + n);
   }
 
-  return colors ? `rgb(${colors.map((n) => parseInt(n, 16)).join(', ')})` : '';
+  return colors
+    ? `rgb${colors.length === 4 ? 'a' : ''}(${colors
+        .map((n, index) => {
+          return index < 3 ? parseInt(n, 16) : Math.round((parseInt(n, 16) / 255) * 1000) / 1000;
+        })
+        .join(', ')})`
+    : '';
 }
 
 function intToHex(int) {


Test patch:
diff --git a/packages/material-ui/src/styles/colorManipulator.test.js b/packages/material-ui/src/styles/colorManipulator.test.js
--- a/packages/material-ui/src/styles/colorManipulator.test.js
+++ b/packages/material-ui/src/styles/colorManipulator.test.js
@@ -69,6 +69,10 @@ describe('utils/colorManipulator', () => {
     it('converts a long hex color to an rgb color` ', () => {
       expect(hexToRgb('#a94fd3')).to.equal('rgb(169, 79, 211)');
     });
+
+    it('converts a long alpha hex color to an argb color` ', () => {
+      expect(hexToRgb('#111111f8')).to.equal('rgba(17, 17, 17, 0.973)');
+    });
   });
 
   describe('rgbToHex', () => {
@@ -125,6 +129,14 @@ describe('utils/colorManipulator', () => {
       const output2 = decomposeColor(output1);
       expect(output1).to.deep.equal(output2);
     });
+
+    it('converts rgba hex', () => {
+      const decomposed = decomposeColor('#111111f8');
+      expect(decomposed).to.deep.equal({
+        type: 'rgba',
+        values: [17, 17, 17, 0.973],
+      });
+    });
   });
 
   describe('getContrastRatio', () => {