Repository: postcss/autoprefixer (js)

Issue:
[css-grid] grid-gap is not retained through media queries
I wrote this test css-grid code and realised that the `grid-gap` functionality wasn't getting carried through media queries.

````css
/* changing a grid-template with a media-query */
.grid {
  display: grid;
  grid-gap: 10px; /* grid-gap defined here */
  grid-template:
    "a   b" 100px
    "c   d" 100px
    "e   f" 100px /
    1fr  1fr;
}

.cell-A {
  grid-area: a;
}

.cell-B {
  grid-area: b;
}

.cell-C {
  grid-area: c;
}

.cell-D {
  grid-area: d;
}

.cell-E {
  grid-area: e;
}

.cell-F {
  grid-area: f;
}

@media (min-width: 600px){
  .grid {
    /* grid-gap setting should be remembered here */
    grid-template-areas:
      "a   b   c"
      "d   e   f";
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: repeat(2, 100px);
  }
}
````
It compiles into this:
````css
.grid {
  display: -ms-grid;
  display: grid;
  grid-gap: 10px;
  -ms-grid-rows: 100px 10px 100px 10px 100px;
  -ms-grid-columns: 1fr 10px 1fr;
      grid-template: "a   b" 100px
 "c   d" 100px
 "e   f" 100px /
 1fr  1fr;
}

.cell-A {
  -ms-grid-row: 1;
  -ms-grid-column: 1;
  grid-area: a;
}

.cell-B {
  -ms-grid-row: 1;
  -ms-grid-column: 3;
  grid-area: b;
}

.cell-C {
  -ms-grid-row: 3;
  -ms-grid-column: 1;
  grid-area: c;
}

.cell-D {
  -ms-grid-row: 3;
  -ms-grid-column: 3;
  grid-area: d;
}

.cell-E {
  -ms-grid-row: 5;
  -ms-grid-column: 1;
  grid-area: e;
}

.cell-F {
  -ms-grid-row: 5;
  -ms-grid-column: 3;
  grid-area: f;
}

@media (min-width: 600px) {
  .grid {
        grid-template-areas: "a   b   c"
 "d   e   f";

    /* !!! IT HAS FORGOTTEN ABOUT THE GRID-GAP SETTING !!! */

    -ms-grid-columns: (1fr)[3];
    grid-template-columns: repeat(3, 1fr);
    -ms-grid-rows: (100px)[2];
    grid-template-rows: repeat(2, 100px);
  }
  .cell-A {
    -ms-grid-row: 1;
    -ms-grid-column: 1;
  }
  .cell-B {
    -ms-grid-row: 1;
    -ms-grid-column: 2;
  }
  .cell-C {
    -ms-grid-row: 1;
    -ms-grid-column: 3;
  }
  .cell-D {
    -ms-grid-row: 2;
    -ms-grid-column: 1;
  }
  .cell-E {
    -ms-grid-row: 2;
    -ms-grid-column: 2;
  }
  .cell-F {
    -ms-grid-row: 2;
    -ms-grid-column: 3;
  }
}
````

My idea is that if the `grid-area` name is consistent throughout various media-queries then it is safe to assume that it is looking at the same grid.

If the `grid-gap` setting is defined outside of a media query, then that gap setting needs to be applied as a default across all grids that share that same area name.

````css
.grid {
  display: grid;
  grid-gap: 10px;/* grid-gap defined */
  grid-template:
    "a   b" 100px
    "c   d" 100px
    "e   f" 100px /
    1fr  1fr;
}


@media (min-width: 600px){
  .grid {
    /* grid-gap: 10px; << grid-gap inherited */
    grid-template-areas:
      "a   b   c"
      "d   e   f";
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: repeat(2, 100px);
  }
}
````

If a `grid-gap` setting is applied inside of a media query, it will only be applied as a default to other grids that live inside media queries that agree with the `grid-gap` media query.

````css
.grid {
  display: grid;
  /* grid-gap ignored */
  grid-template:
    "a   b" 100px
    "c   d" 100px
    "e   f" 100px /
    1fr  1fr;
}


@media (min-width: 600px){
  .grid {
    grid-gap: 10px;/* grid-gap defined */
    grid-template-areas:
      "a   b   c"
      "d   e   f";
    grid-template-columns: repeat(3, 1fr);
    grid-template-rows: repeat(2, 100px);
  }
}

@media (min-width: 900px){
  .grid {
    /* grid-gap: 10px; << grid-gap inherited */
    grid-template-areas:
      "a   b   c   d   e   f";
    grid-template-columns: repeat(6, 1fr);
    grid-template-rows: 100px;
  }
}

@media (max-width: 400px){
  .grid {
    /* grid-gap ignored */
    grid-template-areas:
      "a"
      "b"
      "c"
      "d"
      "e"
      "f";
    grid-template-columns: 1fr;
    grid-template-rows: repeat(6, 100px);
  }
}
````

If there is a conflict then the `grid-gap` setting that is defined last in the style sheet should have the priority.

Pull request:
 [grid-gap] retain gaps through media
Fixes #1045

cc @Dan503

Patch:
diff --git a/lib/hacks/grid-row-column.js b/lib/hacks/grid-row-column.js
index b9df9b3..7a722bb 100644
--- a/lib/hacks/grid-row-column.js
+++ b/lib/hacks/grid-row-column.js
@@ -11,7 +11,15 @@ class GridRowColumn extends Declaration {
     if (prefix !== '-ms-') return super.insert(decl, prefix, prefixes)
 
     let values = utils.parse(decl)
-    let [start, span] = utils.translate(values, 0, 1);
+    let [start, span] = utils.translate(values, 0, 1)
+
+    let hasStartValueSpan = (
+      values[0] && values[0].includes('span')
+    )
+
+    if (hasStartValueSpan) {
+      span = values[0].join('').replace(/\D/g, '')
+    }
 
     [
       [decl.prop, start],
diff --git a/lib/hacks/grid-template-areas.js b/lib/hacks/grid-template-areas.js
index ab429bb..5a6929c 100644
--- a/lib/hacks/grid-template-areas.js
+++ b/lib/hacks/grid-template-areas.js
@@ -6,7 +6,8 @@ let {
   prefixTrackValue,
   getGridGap,
   warnGridGap,
-  warnDuplicateNames
+  warnDuplicateNames,
+  inheritGridGap
 } = require('./grid-utils')
 
 function getGridRows (tpl) {
@@ -26,6 +27,7 @@ class GridTemplateAreas extends Declaration {
     let hasRows = false
     let parent = decl.parent
     let gap = getGridGap(decl)
+    let inheritedGap = inheritGridGap(decl, gap)
 
     // remove already prefixed rows and columns
     // without gutter to prevent doubling prefixes
@@ -36,17 +38,41 @@ class GridTemplateAreas extends Declaration {
       if (trackDecl.prop === 'grid-template-rows') {
         hasRows = true
         let { prop, value } = trackDecl
-        trackDecl.cloneBefore({
-          prop: prefixTrackProp({ prop, prefix }),
-          value: prefixTrackValue({ value, gap: gap.row })
-        })
+
+        /**
+         * we must insert inherited gap values in some cases:
+         * if we are inside media query && if we have no grid-gap value
+        */
+        if (inheritedGap) {
+          trackDecl.cloneBefore({
+            prop: prefixTrackProp({ prop, prefix }),
+            value: prefixTrackValue({ value, gap: inheritedGap.row })
+          })
+        } else {
+          trackDecl.cloneBefore({
+            prop: prefixTrackProp({ prop, prefix }),
+            value: prefixTrackValue({ value, gap: gap.row })
+          })
+        }
       } else {
         hasColumns = true
         let { prop, value } = trackDecl
-        trackDecl.cloneBefore({
-          prop: prefixTrackProp({ prop, prefix }),
-          value: prefixTrackValue({ value, gap: gap.column })
-        })
+
+        /**
+         * we must insert inherited gap values in some cases:
+         * if we are inside media query && if we have no grid-gap value
+        */
+        if (inheritedGap) {
+          trackDecl.cloneBefore({
+            prop: prefixTrackProp({ prop, prefix }),
+            value: prefixTrackValue({ value, gap: inheritedGap.column })
+          })
+        } else {
+          trackDecl.cloneBefore({
+            prop: prefixTrackProp({ prop, prefix }),
+            value: prefixTrackValue({ value, gap: gap.column })
+          })
+        }
       }
     })
 
diff --git a/lib/hacks/grid-template.js b/lib/hacks/grid-template.js
index 8c2d5cf..afe7087 100644
--- a/lib/hacks/grid-template.js
+++ b/lib/hacks/grid-template.js
@@ -4,7 +4,8 @@ let {
   insertAreas,
   getGridGap,
   warnGridGap,
-  warnDuplicateNames
+  warnDuplicateNames,
+  inheritGridGap
 } = require('./grid-utils')
 
 class GridTemplate extends Declaration {
@@ -21,13 +22,20 @@ class GridTemplate extends Declaration {
     }
 
     let gap = getGridGap(decl)
+
+    /**
+     * we must insert inherited gap values in some cases:
+     * if we are inside media query && if we have no grid-gap value
+    */
+    let inheritedGap = inheritGridGap(decl, gap)
+
     let {
       rows,
       columns,
       areas
     } = parseTemplate({
       decl,
-      gap
+      gap: inheritedGap || gap
     })
 
     let hasAreas = Object.keys(areas).length > 0
diff --git a/lib/hacks/grid-utils.js b/lib/hacks/grid-utils.js
index 0e761b9..f3461b7 100644
--- a/lib/hacks/grid-utils.js
+++ b/lib/hacks/grid-utils.js
@@ -561,6 +561,89 @@ function getGridGap (decl) {
   return gap
 }
 
+/**
+ * parse media parameters (for example 'min-width: 500px')
+ * @param  {String} params parameter to parse
+ * @return {}
+ */
+function parseMediaParams (params) {
+  let parsed = parser(params)
+  let prop
+  let value
+
+  parsed.walk(node => {
+    if (node.type === 'word' && /min|max/g.test(node.value)) {
+      prop = node.value
+    } else if (node.value.includes('px')) {
+      value = parseInt(node.value.replace(/\D/g, ''))
+    }
+  })
+
+  return [prop, value]
+}
+
+/**
+ * inherit grid gap values from the closest rule above
+ * with the same selector
+ * @param  {Declaration} decl
+ * @param  {Object} gap gap values
+ * @return {Object | Boolean} return gap values or false (if not found)
+ */
+function inheritGridGap (decl, gap) {
+  let rule = decl.parent
+  let mediaRule = getParentMedia(rule)
+  let root = rule.root()
+  if (Object.keys(gap).length > 0 || !mediaRule) {
+    return false
+  }
+
+  // e.g ['min-width']
+  let [prop] = parseMediaParams(mediaRule.params)
+
+  // find the closest rule with the same selector
+  let closestRuleGap
+  root.walkRules(rule.selector, r => {
+    let gridGap
+
+    // abort if checking the same rule
+    if (rule.toString() === r.toString()) {
+      return false
+    }
+
+    // find grid-gap values
+    r.walkDecls('grid-gap', d => (gridGap = getGridGap(d)))
+
+    // skip rule without gaps
+    if (!gridGap || Object.keys(gridGap).length === 0) {
+      return true
+    }
+
+    let media = getParentMedia(r)
+
+    if (media) {
+      // if we are inside media, we need to check that media props match
+      // e.g ('min-width' === 'min-width')
+      let propToCompare = parseMediaParams(media.params)[0]
+      if (propToCompare === prop) {
+        closestRuleGap = gridGap
+        return true
+      }
+    } else {
+      closestRuleGap = gridGap
+      return true
+    }
+
+    return undefined
+  })
+
+  // if we find the closest gap object
+  if (closestRuleGap && Object.keys(closestRuleGap).length > 0) {
+    return closestRuleGap
+  } else {
+    return false
+  }
+}
+
 function warnGridGap ({
   gap,
   hasColumns,
@@ -588,5 +671,6 @@ module.exports = {
   prefixTrackValue,
   getGridGap,
   warnGridGap,
-  warnDuplicateNames
+  warnDuplicateNames,
+  inheritGridGap
 }
diff --git a/package.json b/package.json
index 80e9f62..de960d9 100644
--- a/package.json
+++ b/package.json
@@ -70,7 +70,7 @@
   "size-limit": [
     {
       "path": "build/lib/autoprefixer.js",
-      "limit": "100 KB"
+      "limit": "101 KB"
     }
   ],
   "eslintConfig": {


Test patch:
diff --git a/test/cases/grid-gap.css b/test/cases/grid-gap.css
index 3d06f8f..bd7df11 100644
--- a/test/cases/grid-gap.css
+++ b/test/cases/grid-gap.css
@@ -66,3 +66,110 @@
       "nav-shortcut foot-shortcut foot-shortcut"
       / 1fr 1fr 1fr;
 }
+
+.grid {
+  display: grid;
+  grid-template:
+    "a   b" 100px
+    "c   d" 100px
+    "e   f" 100px /
+    1fr  1fr;
+}
+
+.grid-2 {
+  display: grid;
+  grid-gap: 50px;
+  grid-template:
+    "x   x" 100px
+    "y   y" 100px
+    "z   z" 100px /
+    1fr  1fr;
+}
+
+@media (min-width: 600px){
+  .grid {
+    grid-gap: 10px;
+    grid-template-areas:
+      "a   b   c"
+      "d   e   f";
+    grid-template-columns: repeat(3, 1fr);
+    grid-template-rows: repeat(2, 100px);
+  }
+}
+
+@media (min-width: 700px){
+  .grid {
+    grid-gap: 20px;
+    grid-template-areas:
+      "a   b   c"
+      "d   e   f";
+    grid-template-columns: repeat(3, 1fr);
+    grid-template-rows: repeat(2, 100px);
+  }
+}
+
+@media (min-width: 900px){
+  .grid {
+    grid-template-areas:
+      "a   b   c   d   e   f";
+    grid-template-columns: repeat(6, 1fr);
+    grid-template-rows: 100px;
+  }
+}
+
+@media (max-width: 400px){
+  .grid {
+    grid-template-areas:
+      "a"
+      "b"
+      "c"
+      "d"
+      "e"
+      "f";
+    grid-template-columns: 1fr;
+    grid-template-rows: repeat(6, 100px);
+  }
+}
+
+@media (max-width: 1400px){
+  .grid-2 {
+    grid-template:
+      "x   y   z" 100px
+      "x   y   z" 100px
+      / 1fr 1fr;
+  }
+}
+
+@media (max-width: 1000px){
+  .grid-2 {
+    grid-gap: 20px;
+    grid-template-areas:
+      "x   y   z"
+      "x   y   z";
+    grid-template-columns: repeat(3, 1fr);
+    grid-template-rows: repeat(2, 100px);
+  }
+}
+
+@media (max-width: 700px){
+  .grid-2 {
+    grid-template-areas:
+      "x   x   y   y   z   z";
+    grid-template-columns: repeat(6, 1fr);
+    grid-template-rows: 100px;
+  }
+}
+
+@media (max-width: 400px){
+  .grid-2 {
+    grid-template-areas:
+      "x"
+      "x"
+      "y"
+      "y"
+      "z"
+      "z";
+    grid-template-columns: 1fr;
+    grid-template-rows: repeat(6, 100px);
+  }
+}
diff --git a/test/cases/grid-gap.out.css b/test/cases/grid-gap.out.css
index 40f6113..cc0262b 100644
--- a/test/cases/grid-gap.out.css
+++ b/test/cases/grid-gap.out.css
@@ -98,3 +98,132 @@
       "nav-shortcut foot-shortcut foot-shortcut"
       / 1fr 1fr 1fr;
 }
+
+.grid {
+  display: -ms-grid;
+  display: grid;
+  -ms-grid-rows: 100px 100px 100px;
+  -ms-grid-columns: 1fr 1fr;
+  grid-template:
+    "a   b" 100px
+    "c   d" 100px
+    "e   f" 100px /
+    1fr  1fr;
+}
+
+.grid-2 {
+  display: -ms-grid;
+  display: grid;
+  grid-gap: 50px;
+  -ms-grid-rows: 100px 50px 100px 50px 100px;
+  -ms-grid-columns: 1fr 50px 1fr;
+  grid-template:
+    "x   x" 100px
+    "y   y" 100px
+    "z   z" 100px /
+    1fr  1fr;
+}
+
+@media (min-width: 600px){
+  .grid {
+    grid-gap: 10px;
+    grid-template-areas:
+      "a   b   c"
+      "d   e   f";
+    -ms-grid-columns: 1fr 10px 1fr 10px 1fr;
+    grid-template-columns: repeat(3, 1fr);
+    -ms-grid-rows: 100px 10px 100px;
+    grid-template-rows: repeat(2, 100px);
+  }
+}
+
+@media (min-width: 700px){
+  .grid {
+    grid-gap: 20px;
+    grid-template-areas:
+      "a   b   c"
+      "d   e   f";
+    -ms-grid-columns: 1fr 20px 1fr 20px 1fr;
+    grid-template-columns: repeat(3, 1fr);
+    -ms-grid-rows: 100px 20px 100px;
+    grid-template-rows: repeat(2, 100px);
+  }
+}
+
+@media (min-width: 900px){
+  .grid {
+    grid-template-areas:
+      "a   b   c   d   e   f";
+    -ms-grid-columns: 1fr 20px 1fr 20px 1fr 20px 1fr 20px 1fr 20px 1fr;
+    grid-template-columns: repeat(6, 1fr);
+    -ms-grid-rows: 100px;
+    grid-template-rows: 100px;
+  }
+}
+
+@media (max-width: 400px){
+  .grid {
+    grid-template-areas:
+      "a"
+      "b"
+      "c"
+      "d"
+      "e"
+      "f";
+    -ms-grid-columns: 1fr;
+    grid-template-columns: 1fr;
+    -ms-grid-rows: (100px)[6];
+    grid-template-rows: repeat(6, 100px);
+  }
+}
+
+@media (max-width: 1400px){
+  .grid-2 {
+    -ms-grid-rows: 100px 50px 100px;
+    -ms-grid-columns: 1fr 50px 1fr;
+    grid-template:
+      "x   y   z" 100px
+      "x   y   z" 100px
+      / 1fr 1fr;
+  }
+}
+
+@media (max-width: 1000px){
+  .grid-2 {
+    grid-gap: 20px;
+    grid-template-areas:
+      "x   y   z"
+      "x   y   z";
+    -ms-grid-columns: 1fr 20px 1fr 20px 1fr;
+    grid-template-columns: repeat(3, 1fr);
+    -ms-grid-rows: 100px 20px 100px;
+    grid-template-rows: repeat(2, 100px);
+  }
+}
+
+@media (max-width: 700px){
+  .grid-2 {
+    grid-template-areas:
+      "x   x   y   y   z   z";
+    -ms-grid-columns: 1fr 20px 1fr 20px 1fr 20px 1fr 20px 1fr 20px 1fr;
+    grid-template-columns: repeat(6, 1fr);
+    -ms-grid-rows: 100px;
+    grid-template-rows: 100px;
+  }
+}
+
+@media (max-width: 400px){
+  .grid-2 {
+    grid-template-areas:
+      "x"
+      "x"
+      "y"
+      "y"
+      "z"
+      "z";
+    -ms-grid-columns: 1fr;
+    grid-template-columns: 1fr;
+    -ms-grid-rows: 100px 20px 100px 20px 100px 20px 100px 20px 100px 20px 100px;
+    grid-template-rows: repeat(6, 100px);
+  }
+}
diff --git a/test/cases/grid.css b/test/cases/grid.css
index 6fd892c..c908ae1 100644
--- a/test/cases/grid.css
+++ b/test/cases/grid.css
@@ -104,3 +104,11 @@
   grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
   grid-template-rows: repeat(auto-fit, minmax(400px, 1fr));
 }
+
+.baz {
+  grid-column: span 3;
+}
+
+.bar {
+  grid-row: span 3;
+}
diff --git a/test/cases/grid.disabled.css b/test/cases/grid.disabled.css
index 637b1fa..c645c71 100644
--- a/test/cases/grid.disabled.css
+++ b/test/cases/grid.disabled.css
@@ -106,3 +106,11 @@
   grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
   grid-template-rows: repeat(auto-fit, minmax(400px, 1fr));
 }
+
+.baz {
+  grid-column: span 3;
+}
+
+.bar {
+  grid-row: span 3;
+}
diff --git a/test/cases/grid.out.css b/test/cases/grid.out.css
index 9e2eb5e..a03342c 100644
--- a/test/cases/grid.out.css
+++ b/test/cases/grid.out.css
@@ -140,3 +140,13 @@
   grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
   grid-template-rows: repeat(auto-fit, minmax(400px, 1fr));
 }
+
+.baz {
+  -ms-grid-column-span: 3;
+  grid-column: span 3;
+}
+
+.bar {
+  -ms-grid-row-span: 3;
+  grid-row: span 3;
+}