diff --git a/lib/hacks/grid-rows-columns.js b/lib/hacks/grid-rows-columns.js index b41d651b2..4be2db2fe 100644 --- a/lib/hacks/grid-rows-columns.js +++ b/lib/hacks/grid-rows-columns.js @@ -1,5 +1,8 @@ const Declaration = require('../declaration'); -const utils = require('./grid-utils'); +const { + prefixTrackProp, + prefixTrackValue +} = require('./grid-utils'); class GridRowsColumns extends Declaration { @@ -13,7 +16,7 @@ class GridRowsColumns extends Declaration { */ prefixed(prop, prefix) { if (prefix === '-ms-') { - return prefix + prop.replace('template-', ''); + return prefixTrackProp({ prop, prefix }); } else { return super.prefixed(prop, prefix); } @@ -31,7 +34,7 @@ class GridRowsColumns extends Declaration { */ set(decl, prefix) { if (prefix === '-ms-' && decl.value.indexOf('repeat(') !== -1) { - decl.value = utils.changeRepeat(decl.value); + decl.value = prefixTrackValue({ value: decl.value }); } return super.set(decl, prefix); } diff --git a/lib/hacks/grid-template-areas.js b/lib/hacks/grid-template-areas.js index 3a2afd7b5..e8b052936 100644 --- a/lib/hacks/grid-template-areas.js +++ b/lib/hacks/grid-template-areas.js @@ -1,7 +1,11 @@ const Declaration = require('../declaration'); const { parseGridAreas, - insertAreas + insertAreas, + prefixTrackProp, + prefixTrackValue, + getGridGap, + warnGridGap } = require('./grid-utils'); @@ -19,7 +23,59 @@ class GridTemplateAreas extends Declaration { insert(decl, prefix, prefixes, result) { if (prefix !== '-ms-') return super.insert(decl, prefix, prefixes); - const areas = parseGridAreas(getGridRows(decl.value)); + let hasColumns = false; + let hasRows = false; + const parent = decl.parent; + const gap = getGridGap(decl); + + // remove already prefixed rows and columns + // without gutter to prevent doubling prefixes + parent.walkDecls(/-ms-grid-(rows|columns)/, i => i.remove()); + + // add empty tracks to rows and columns + parent.walkDecls(/grid-template-(rows|columns)/, (trackDecl) => { + if (trackDecl.prop === 'grid-template-rows') { + hasRows = true; + const { prop, value } = trackDecl; + trackDecl.cloneBefore({ + prop: prefixTrackProp({ prop, prefix }), + value: prefixTrackValue({ value, gap: gap.row }) + }); + } else { + hasColumns = true; + const { prop, value } = trackDecl; + trackDecl.cloneBefore({ + prop: prefixTrackProp({ prop, prefix }), + value: prefixTrackValue({ value, gap: gap.column }) + }); + } + }); + + const gridRows = getGridRows(decl.value); + + if (hasColumns && !hasRows && gap.row && gridRows.length > 1) { + decl.cloneBefore({ + prop: '-ms-grid-rows', + value: prefixTrackValue({ + value: `repeat(${gridRows.length}, auto)`, + gap: gap.row + }), + raws: {} + }); + } + + // warnings + warnGridGap({ + gap, + hasColumns, + decl, + result + }); + + const areas = parseGridAreas({ + rows: gridRows, + gap + }); insertAreas(areas, decl, result); diff --git a/lib/hacks/grid-template.js b/lib/hacks/grid-template.js index ef179df70..c441f41f2 100644 --- a/lib/hacks/grid-template.js +++ b/lib/hacks/grid-template.js @@ -1,7 +1,9 @@ const Declaration = require('../declaration'); const { parseTemplate, - insertAreas + insertAreas, + getGridGap, + warnGridGap } = require('./grid-utils'); class GridTemplate extends Declaration { @@ -21,14 +23,28 @@ class GridTemplate extends Declaration { return undefined; } + const gap = getGridGap(decl); const { rows, columns, areas - } = parseTemplate(decl); + } = parseTemplate({ + decl, + gap + }); + const hasAreas = Object.keys(areas).length > 0; + const hasRows = Boolean(rows); + const hasColumns = Boolean(columns); + + warnGridGap({ + gap, + hasColumns, + decl, + result + }); - if (rows && columns || hasAreas) { + if (hasRows && hasColumns || hasAreas) { decl.cloneBefore({ prop: '-ms-grid-rows', value: rows, @@ -36,7 +52,7 @@ class GridTemplate extends Declaration { }); } - if (columns) { + if (hasColumns) { decl.cloneBefore({ prop: '-ms-grid-columns', value: columns, diff --git a/lib/hacks/grid-utils.js b/lib/hacks/grid-utils.js index 1decbab6a..f8c1d03d5 100644 --- a/lib/hacks/grid-utils.js +++ b/lib/hacks/grid-utils.js @@ -77,40 +77,68 @@ function insertDecl(decl, prop, value) { } } -// Transform repeat +// Track transforms -function transformRepeat({ nodes }) { - const repeat = nodes.reduce((result, node) => { +function prefixTrackProp({ prop, prefix }) { + return prefix + prop.replace('template-', ''); +} + +function transformRepeat({ nodes }, { gap }) { + const { + count, + size + } = nodes.reduce((result, node) => { if (node.type === 'div' && node.value === ',') { - result.key = 'function'; + result.key = 'size'; } else { result[result.key].push(parser.stringify(node)); } return result; }, { key: 'count', - function: [], + size: [], count: [] }); - return `(${repeat.function.join('')})[${repeat.count.join('')}]`; + + if (gap) { + const val = []; + for (let i = 1; i <= count; i++) { + if (gap && i > 1) { + val.push(gap); + } + val.push(size.join()); + } + return val.join(' '); + } + + return `(${size.join('')})[${count.join('')}]`; } -function changeRepeat(value) { +function prefixTrackValue({ value, gap }) { const result = parser(value) .nodes - .map(i => { - if (i.type === 'function' && i.value === 'repeat') { - return { + .reduce((nodes, node) => { + if (node.type === 'function' && node.value === 'repeat') { + return nodes.concat({ type: 'word', - value: transformRepeat(i) - }; + value: transformRepeat(node, { gap }) + }); } - return i; - }); + if (gap && node.type === 'space') { + return nodes.concat({ + type: 'space', + value: ' ' + }, { + type: 'word', + value: gap + }, node); + } + return nodes.concat(node); + }, []); + return parser.stringify(result); } - // Parse grid-template-areas const DOTS = /^\.+$/; @@ -123,17 +151,31 @@ function getColumns(line) { return line.trim().split(/\s+/g); } -function parseGridAreas(rows) { +function parseGridAreas({ + rows, + gap +}) { return rows.reduce((areas, line, rowIndex) => { + + if (gap.row) rowIndex *= 2; + if (line.trim() === '') return areas; + getColumns(line).forEach((area, columnIndex) => { + if (DOTS.test(area)) return; + + if (gap.column) columnIndex *= 2; + if (typeof areas[area] === 'undefined') { + areas[area] = { column: track(columnIndex + 1, columnIndex + 2), row: track(rowIndex + 1, rowIndex + 2) }; + } else { + const { column, row } = areas[area]; column.start = Math.min(column.start, columnIndex + 1); @@ -143,8 +185,10 @@ function parseGridAreas(rows) { row.start = Math.min(row.start, rowIndex + 1); row.end = Math.max(row.end, rowIndex + 2); row.span = row.end - row.start; + } }); + return areas; }, {}); } @@ -156,7 +200,10 @@ function testTrack(node) { return node.type === 'word' && /^\[.+\]$/.test(node.value); } -function parseTemplate(decl) { +function parseTemplate({ + decl, + gap +}) { const gridTemplate = parser(decl.value) .nodes .reduce((result, node) => { @@ -171,11 +218,7 @@ function parseTemplate(decl) { // values and function if (type === 'word' || type === 'function') { - if (type === 'function' && value === 'repeat') { - result[result.key].push(transformRepeat(node)); - } else { - result[result.key].push(parser.stringify(node)); - } + result[result.key].push(parser.stringify(node)); } // devider(/) @@ -190,10 +233,20 @@ function parseTemplate(decl) { rows: [], areas: [] }); + return { - areas: parseGridAreas(gridTemplate.areas), - columns: gridTemplate.columns.join(' '), - rows: gridTemplate.rows.join(' ') + areas: parseGridAreas({ + rows: gridTemplate.areas, + gap + }), + columns: prefixTrackValue({ + value: gridTemplate.columns.join(' '), + gap: gap.column + }), + rows: prefixTrackValue({ + value: gridTemplate.rows.join(' '), + gap: gap.row + }) }; } @@ -291,12 +344,50 @@ function insertAreas(areas, decl, result) { } } +// Gap utils + +function getGridGap(decl) { + + let gap = {}; + + // try to find gap + decl.parent.walkDecls(/grid(-(row|column))?-gap/, ({ prop, value }) => { + if (prop === 'grid-gap') { + gap.column = value; + gap.row = value; + } + if (prop === 'grid-row-gap') gap.row = value; + if (prop === 'grid-column-gap') gap.column = value; + }); + + return gap; +} + +function warnGridGap({ + gap, + hasColumns, + decl, + result +}) { + const hasBothGaps = gap.row && gap.column; + if (!hasColumns && (hasBothGaps || gap.column && !gap.row)) { + delete gap.column; + decl.warn( + result, + 'Can not impliment grid-gap without grid-tamplate-columns' + ); + } +} + module.exports = { parse, translate, - changeRepeat, parseTemplate, parseGridAreas, insertAreas, - insertDecl + insertDecl, + prefixTrackProp, + prefixTrackValue, + getGridGap, + warnGridGap }; diff --git a/package.json b/package.json index b0bd3828b..56f5dc07d 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "size-limit": [ { "path": "build/lib/autoprefixer.js", - "limit": "97 KB" + "limit": "98 KB" } ], "eslintConfig": { diff --git a/test/autoprefixer.test.js b/test/autoprefixer.test.js index c9c76aa56..11bb52034 100644 --- a/test/autoprefixer.test.js +++ b/test/autoprefixer.test.js @@ -66,6 +66,7 @@ const overscroller = autoprefixer({ function prefixer(name) { if ( name === 'grid' || + name === 'grid-gap' || name === 'grid-area' || name === 'grid-template' || name === 'grid-template-areas' @@ -144,7 +145,7 @@ const COMMONS = [ 'advanced-filter', 'element', 'image-set', 'image-rendering', 'mask-border', 'writing-mode', 'cross-fade', 'gradient-fix', 'text-emphasis-position', 'grid', 'grid-area', 'grid-template', - 'grid-template-areas', 'color-adjust' + 'grid-template-areas', 'grid-gap', 'color-adjust' ]; it('throws on wrong options', () => { @@ -268,6 +269,7 @@ it('removes unnecessary prefixes', () => { if (type === 'mistakes' ) continue; if (type === 'flex-rewrite' ) continue; if (type === 'grid' ) continue; + if (type === 'grid-gap' ) continue; if (type === 'grid-area' ) continue; if (type === 'grid-template' ) continue; if (type === 'grid-template-areas' ) continue; @@ -498,8 +500,18 @@ describe('hacks', () => { expect(result.warnings().map(i => i.toString())).toEqual([ 'autoprefixer: :36:5: Can not prefix grid-column-end ' + '(grid-column-start is not found)', - 'autoprefixer: :38:5: Can not find grid areas: ' + - 'head, nav, main, foot' + 'autoprefixer: :39:5: Can not impliment grid-gap ' + + 'without grid-tamplate-columns', + 'autoprefixer: :39:5: Can not find grid areas: ' + + 'head, nav, main, foot', + 'autoprefixer: :47:5: Can not impliment grid-gap ' + + 'without grid-tamplate-columns', + 'autoprefixer: :47:5: Can not find grid areas: a', + 'autoprefixer: :55:5: Can not impliment grid-gap ' + + 'without grid-tamplate-columns', + 'autoprefixer: :55:5: Can not find grid areas: b', + 'autoprefixer: :63:5: Can not find grid areas: c', + 'autoprefixer: :71:5: Can not find grid areas: d' ]); }); diff --git a/test/cases/grid-gap.css b/test/cases/grid-gap.css new file mode 100644 index 000000000..ff9f00056 --- /dev/null +++ b/test/cases/grid-gap.css @@ -0,0 +1,50 @@ +.a { + grid-gap: 1rem; + grid-template-rows: 1fr minmax(100px, 1fr) 2fr; + grid-template-columns: repeat(3, 1fr); + grid-template-areas: + "head head head" + "nav main main" + "nav foot foot"; +} + +.b { + grid-area: head; +} + +.c { + grid-area: main; +} + +.d { + grid-area: nav; +} + +.e { + grid-area: foot; +} + +.a-shortcut { + grid-gap: 1rem; + grid-template: + "head-shortcut head-shortcut head-shortcut" 1fr + "nav-shortcut main-shortcut main-shortcut" minmax(100px, 1fr) + "nav-shortcut foot-shortcut foot-shortcut" 2fr / + 1fr 100px 1fr; +} + +.b-shortcut { + grid-area: head-shortcut; +} + +.c-shortcut { + grid-area: main-shortcut; +} + +.d-shortcut { + grid-area: nav-shortcut; +} + +.e-shortcut { + grid-area: foot-shortcut; +} diff --git a/test/cases/grid-gap.out.css b/test/cases/grid-gap.out.css new file mode 100644 index 000000000..2d790e722 --- /dev/null +++ b/test/cases/grid-gap.out.css @@ -0,0 +1,78 @@ +.a { + grid-gap: 1rem; + -ms-grid-rows: 1fr 1rem minmax(100px, 1fr) 1rem 2fr; + grid-template-rows: 1fr minmax(100px, 1fr) 2fr; + -ms-grid-columns: 1fr 1rem 1fr 1rem 1fr; + grid-template-columns: repeat(3, 1fr); + grid-template-areas: + "head head head" + "nav main main" + "nav foot foot"; +} + +.b { + -ms-grid-row: 1; + -ms-grid-column: 1; + -ms-grid-column-span: 5; + grid-area: head; +} + +.c { + -ms-grid-row: 3; + -ms-grid-column: 3; + -ms-grid-column-span: 3; + grid-area: main; +} + +.d { + -ms-grid-row: 3; + -ms-grid-row-span: 3; + -ms-grid-column: 1; + grid-area: nav; +} + +.e { + -ms-grid-row: 5; + -ms-grid-column: 3; + -ms-grid-column-span: 3; + grid-area: foot; +} + +.a-shortcut { + grid-gap: 1rem; + -ms-grid-rows: 1fr 1rem minmax(100px, 1fr) 1rem 2fr; + -ms-grid-columns: 1fr 1rem 100px 1rem 1fr; + grid-template: + "head-shortcut head-shortcut head-shortcut" 1fr + "nav-shortcut main-shortcut main-shortcut" minmax(100px, 1fr) + "nav-shortcut foot-shortcut foot-shortcut" 2fr / + 1fr 100px 1fr; +} + +.b-shortcut { + -ms-grid-row: 1; + -ms-grid-column: 1; + -ms-grid-column-span: 5; + grid-area: head-shortcut; +} + +.c-shortcut { + -ms-grid-row: 3; + -ms-grid-column: 3; + -ms-grid-column-span: 3; + grid-area: main-shortcut; +} + +.d-shortcut { + -ms-grid-row: 3; + -ms-grid-row-span: 3; + -ms-grid-column: 1; + grid-area: nav-shortcut; +} + +.e-shortcut { + -ms-grid-row: 5; + -ms-grid-column: 3; + -ms-grid-column-span: 3; + grid-area: foot-shortcut; +} diff --git a/test/cases/grid.css b/test/cases/grid.css index 045db6160..a2f3f5b83 100644 --- a/test/cases/grid.css +++ b/test/cases/grid.css @@ -35,11 +35,44 @@ .warn { grid-column-end: 3; grid: subgrid; + grid-gap: 1rem; grid-template-areas: "head head" "nav main" "foot ...."; } +.warn-gap-rows { + grid-gap: 1rem; + grid-template-rows: 1fr 1fr; + grid-template-areas: + "a a" + "a a"; +} + +.warn-gap-rows { + grid-column-gap: 1rem; + grid-template-rows: 1fr 1fr; + grid-template-areas: + "b b" + "b b"; +} + +.warn-gap-columns { + grid-gap: 1rem; + grid-template-columns: 1fr 1fr; + grid-template-areas: + "c c" + "c c"; +} + +.warn-gap-columns { + grid-row-gap: 1rem; + grid-template-columns: 1fr 1fr; + grid-template-areas: + "d d" + "d d"; +} + .unknown { justify-self: start; align-self: start; diff --git a/test/cases/grid.disabled.css b/test/cases/grid.disabled.css index ec77242cc..b153948c1 100644 --- a/test/cases/grid.disabled.css +++ b/test/cases/grid.disabled.css @@ -35,11 +35,44 @@ .warn { grid-column-end: 3; grid: subgrid; + grid-gap: 1rem; grid-template-areas: "head head" "nav main" "foot ...."; } +.warn-gap-rows { + grid-gap: 1rem; + grid-template-rows: 1fr 1fr; + grid-template-areas: + "a a" + "a a"; +} + +.warn-gap-rows { + grid-column-gap: 1rem; + grid-template-rows: 1fr 1fr; + grid-template-areas: + "b b" + "b b"; +} + +.warn-gap-columns { + grid-gap: 1rem; + grid-template-columns: 1fr 1fr; + grid-template-areas: + "c c" + "c c"; +} + +.warn-gap-columns { + grid-row-gap: 1rem; + grid-template-columns: 1fr 1fr; + grid-template-areas: + "d d" + "d d"; +} + .unknown { justify-self: start; -ms-flex-item-align: start; diff --git a/test/cases/grid.out.css b/test/cases/grid.out.css index 0b038ec36..b59d223df 100644 --- a/test/cases/grid.out.css +++ b/test/cases/grid.out.css @@ -58,11 +58,50 @@ -ms-grid-column-span: 3; grid-column-end: 3; grid: subgrid; + grid-gap: 1rem; grid-template-areas: "head head" "nav main" "foot ...."; } +.warn-gap-rows { + grid-gap: 1rem; + -ms-grid-rows: 1fr 1rem 1fr; + grid-template-rows: 1fr 1fr; + grid-template-areas: + "a a" + "a a"; +} + +.warn-gap-rows { + grid-column-gap: 1rem; + -ms-grid-rows: 1fr 1fr; + grid-template-rows: 1fr 1fr; + grid-template-areas: + "b b" + "b b"; +} + +.warn-gap-columns { + grid-gap: 1rem; + -ms-grid-columns: 1fr 1rem 1fr; + grid-template-columns: 1fr 1fr; + -ms-grid-rows: auto 1rem auto; + grid-template-areas: + "c c" + "c c"; +} + +.warn-gap-columns { + grid-row-gap: 1rem; + -ms-grid-columns: 1fr 1fr; + grid-template-columns: 1fr 1fr; + -ms-grid-rows: auto 1rem auto; + grid-template-areas: + "d d" + "d d"; +} + .unknown { -ms-grid-column-align: start; justify-self: start;