grid-utils.js 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147
  1. "use strict";
  2. var parser = require('postcss-value-parser');
  3. var list = require('postcss').list;
  4. var uniq = require('../utils').uniq;
  5. var escapeRegexp = require('../utils').escapeRegexp;
  6. var splitSelector = require('../utils').splitSelector;
  7. function convert(value) {
  8. if (value && value.length === 2 && value[0] === 'span' && parseInt(value[1], 10) > 0) {
  9. return [false, parseInt(value[1], 10)];
  10. }
  11. if (value && value.length === 1 && parseInt(value[0], 10) > 0) {
  12. return [parseInt(value[0], 10), false];
  13. }
  14. return [false, false];
  15. }
  16. function translate(values, startIndex, endIndex) {
  17. var startValue = values[startIndex];
  18. var endValue = values[endIndex];
  19. if (!startValue) {
  20. return [false, false];
  21. }
  22. var _convert = convert(startValue),
  23. start = _convert[0],
  24. spanStart = _convert[1];
  25. var _convert2 = convert(endValue),
  26. end = _convert2[0],
  27. spanEnd = _convert2[1];
  28. if (start && !endValue) {
  29. return [start, false];
  30. }
  31. if (spanStart && end) {
  32. return [end - spanStart, spanStart];
  33. }
  34. if (start && spanEnd) {
  35. return [start, spanEnd];
  36. }
  37. if (start && end) {
  38. return [start, end - start];
  39. }
  40. return [false, false];
  41. }
  42. function parse(decl) {
  43. var node = parser(decl.value);
  44. var values = [];
  45. var current = 0;
  46. values[current] = [];
  47. for (var _iterator = node.nodes, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
  48. var _ref;
  49. if (_isArray) {
  50. if (_i >= _iterator.length) break;
  51. _ref = _iterator[_i++];
  52. } else {
  53. _i = _iterator.next();
  54. if (_i.done) break;
  55. _ref = _i.value;
  56. }
  57. var i = _ref;
  58. if (i.type === 'div') {
  59. current += 1;
  60. values[current] = [];
  61. } else if (i.type === 'word') {
  62. values[current].push(i.value);
  63. }
  64. }
  65. return values;
  66. }
  67. function insertDecl(decl, prop, value) {
  68. if (value && !decl.parent.some(function (i) {
  69. return i.prop === "-ms-" + prop;
  70. })) {
  71. decl.cloneBefore({
  72. prop: "-ms-" + prop,
  73. value: value.toString()
  74. });
  75. }
  76. } // Track transforms
  77. function prefixTrackProp(_ref2) {
  78. var prop = _ref2.prop,
  79. prefix = _ref2.prefix;
  80. return prefix + prop.replace('template-', '');
  81. }
  82. function transformRepeat(_ref3, _ref4) {
  83. var nodes = _ref3.nodes;
  84. var gap = _ref4.gap;
  85. var _nodes$reduce = nodes.reduce(function (result, node) {
  86. if (node.type === 'div' && node.value === ',') {
  87. result.key = 'size';
  88. } else {
  89. result[result.key].push(parser.stringify(node));
  90. }
  91. return result;
  92. }, {
  93. key: 'count',
  94. size: [],
  95. count: []
  96. }),
  97. count = _nodes$reduce.count,
  98. size = _nodes$reduce.size; // insert gap values
  99. if (gap) {
  100. var _ret = function () {
  101. size = size.filter(function (i) {
  102. return i.trim();
  103. });
  104. var val = [];
  105. var _loop = function _loop(i) {
  106. size.forEach(function (item, index) {
  107. if (index > 0 || i > 1) {
  108. val.push(gap);
  109. }
  110. val.push(item);
  111. });
  112. };
  113. for (var i = 1; i <= count; i++) {
  114. _loop(i);
  115. }
  116. return {
  117. v: val.join(' ')
  118. };
  119. }();
  120. if (typeof _ret === "object") return _ret.v;
  121. }
  122. return "(" + size.join('') + ")[" + count.join('') + "]";
  123. }
  124. function prefixTrackValue(_ref5) {
  125. var value = _ref5.value,
  126. gap = _ref5.gap;
  127. var result = parser(value).nodes.reduce(function (nodes, node) {
  128. if (node.type === 'function' && node.value === 'repeat') {
  129. return nodes.concat({
  130. type: 'word',
  131. value: transformRepeat(node, {
  132. gap: gap
  133. })
  134. });
  135. }
  136. if (gap && node.type === 'space') {
  137. return nodes.concat({
  138. type: 'space',
  139. value: ' '
  140. }, {
  141. type: 'word',
  142. value: gap
  143. }, node);
  144. }
  145. return nodes.concat(node);
  146. }, []);
  147. return parser.stringify(result);
  148. } // Parse grid-template-areas
  149. var DOTS = /^\.+$/;
  150. function track(start, end) {
  151. return {
  152. start: start,
  153. end: end,
  154. span: end - start
  155. };
  156. }
  157. function getColumns(line) {
  158. return line.trim().split(/\s+/g);
  159. }
  160. function parseGridAreas(_ref6) {
  161. var rows = _ref6.rows,
  162. gap = _ref6.gap;
  163. return rows.reduce(function (areas, line, rowIndex) {
  164. if (gap.row) rowIndex *= 2;
  165. if (line.trim() === '') return areas;
  166. getColumns(line).forEach(function (area, columnIndex) {
  167. if (DOTS.test(area)) return;
  168. if (gap.column) columnIndex *= 2;
  169. if (typeof areas[area] === 'undefined') {
  170. areas[area] = {
  171. column: track(columnIndex + 1, columnIndex + 2),
  172. row: track(rowIndex + 1, rowIndex + 2)
  173. };
  174. } else {
  175. var _areas$area = areas[area],
  176. column = _areas$area.column,
  177. row = _areas$area.row;
  178. column.start = Math.min(column.start, columnIndex + 1);
  179. column.end = Math.max(column.end, columnIndex + 2);
  180. column.span = column.end - column.start;
  181. row.start = Math.min(row.start, rowIndex + 1);
  182. row.end = Math.max(row.end, rowIndex + 2);
  183. row.span = row.end - row.start;
  184. }
  185. });
  186. return areas;
  187. }, {});
  188. } // Parse grid-template
  189. function testTrack(node) {
  190. return node.type === 'word' && /^\[.+\]$/.test(node.value);
  191. }
  192. function verifyRowSize(result) {
  193. if (result.areas.length > result.rows.length) {
  194. result.rows.push('auto');
  195. }
  196. return result;
  197. }
  198. function parseTemplate(_ref7) {
  199. var decl = _ref7.decl,
  200. gap = _ref7.gap;
  201. var gridTemplate = parser(decl.value).nodes.reduce(function (result, node) {
  202. var type = node.type,
  203. value = node.value;
  204. if (testTrack(node) || type === 'space') return result; // area
  205. if (type === 'string') {
  206. result = verifyRowSize(result);
  207. result.areas.push(value);
  208. } // values and function
  209. if (type === 'word' || type === 'function') {
  210. result[result.key].push(parser.stringify(node));
  211. } // divider(/)
  212. if (type === 'div' && value === '/') {
  213. result.key = 'columns';
  214. result = verifyRowSize(result);
  215. }
  216. return result;
  217. }, {
  218. key: 'rows',
  219. columns: [],
  220. rows: [],
  221. areas: []
  222. });
  223. return {
  224. areas: parseGridAreas({
  225. rows: gridTemplate.areas,
  226. gap: gap
  227. }),
  228. columns: prefixTrackValue({
  229. value: gridTemplate.columns.join(' '),
  230. gap: gap.column
  231. }),
  232. rows: prefixTrackValue({
  233. value: gridTemplate.rows.join(' '),
  234. gap: gap.row
  235. })
  236. };
  237. } // Insert parsed grid areas
  238. /**
  239. * Get an array of -ms- prefixed props and values
  240. * @param {Object} [area] area object with column and row data
  241. * @param {Boolean} [addRowSpan] should we add grid-column-row value?
  242. * @param {Boolean} [addColumnSpan] should we add grid-column-span value?
  243. * @return {Array<Object>}
  244. */
  245. function getMSDecls(area, addRowSpan, addColumnSpan) {
  246. if (addRowSpan === void 0) {
  247. addRowSpan = false;
  248. }
  249. if (addColumnSpan === void 0) {
  250. addColumnSpan = false;
  251. }
  252. return [].concat({
  253. prop: '-ms-grid-row',
  254. value: String(area.row.start)
  255. }, area.row.span > 1 || addRowSpan ? {
  256. prop: '-ms-grid-row-span',
  257. value: String(area.row.span)
  258. } : [], {
  259. prop: '-ms-grid-column',
  260. value: String(area.column.start)
  261. }, area.column.span > 1 || addColumnSpan ? {
  262. prop: '-ms-grid-column-span',
  263. value: String(area.column.span)
  264. } : []);
  265. }
  266. function getParentMedia(parent) {
  267. if (parent.type === 'atrule' && parent.name === 'media') {
  268. return parent;
  269. }
  270. if (!parent.parent) {
  271. return false;
  272. }
  273. return getParentMedia(parent.parent);
  274. }
  275. /**
  276. * change selectors for rules with duplicate grid-areas.
  277. * @param {Array<Rule>} rules
  278. * @param {Array<String>} templateSelectors
  279. * @return {Array<Rule>} rules with changed selectors
  280. */
  281. function changeDuplicateAreaSelectors(ruleSelectors, templateSelectors) {
  282. ruleSelectors = ruleSelectors.map(function (selector) {
  283. var selectorBySpace = list.space(selector);
  284. var selectorByComma = list.comma(selector);
  285. if (selectorBySpace.length > selectorByComma.length) {
  286. selector = selectorBySpace.slice(-1).join('');
  287. }
  288. return selector;
  289. });
  290. return ruleSelectors.map(function (ruleSelector) {
  291. var newSelector = templateSelectors.map(function (tplSelector, index) {
  292. var space = index === 0 ? '' : ' ';
  293. return "" + space + tplSelector + " > " + ruleSelector;
  294. });
  295. return newSelector;
  296. });
  297. }
  298. /**
  299. * check if selector of rules are equal
  300. * @param {Rule} ruleA
  301. * @param {Rule} ruleB
  302. * @return {Boolean}
  303. */
  304. function selectorsEqual(ruleA, ruleB) {
  305. return ruleA.selectors.some(function (sel) {
  306. return ruleB.selectors.some(function (s) {
  307. return s === sel;
  308. });
  309. });
  310. }
  311. /**
  312. * Parse data from all grid-template(-areas) declarations
  313. * @param {Root} css css root
  314. * @return {Object} parsed data
  315. */
  316. function parseGridTemplatesData(css) {
  317. var parsed = []; // we walk through every grid-template(-areas) declaration and store
  318. // data with the same area names inside the item
  319. css.walkDecls(/grid-template(-areas)?$/, function (d) {
  320. var rule = d.parent;
  321. var media = getParentMedia(rule);
  322. var gap = getGridGap(d);
  323. var inheritedGap = inheritGridGap(d, gap);
  324. var _parseTemplate = parseTemplate({
  325. decl: d,
  326. gap: inheritedGap || gap
  327. }),
  328. areas = _parseTemplate.areas;
  329. var areaNames = Object.keys(areas); // skip node if it doesn't have areas
  330. if (areaNames.length === 0) {
  331. return true;
  332. } // check parsed array for item that include the same area names
  333. // return index of that item
  334. var index = parsed.reduce(function (acc, _ref8, idx) {
  335. var allAreas = _ref8.allAreas;
  336. var hasAreas = allAreas && areaNames.some(function (area) {
  337. return allAreas.includes(area);
  338. });
  339. return hasAreas ? idx : acc;
  340. }, null);
  341. if (index !== null) {
  342. // index is found, add the grid-template data to that item
  343. var _parsed$index = parsed[index],
  344. allAreas = _parsed$index.allAreas,
  345. rules = _parsed$index.rules; // check if rule has no duplicate area names
  346. var hasNoDuplicates = rules.some(function (r) {
  347. return r.hasDuplicates === false && selectorsEqual(r, rule);
  348. });
  349. var duplicatesFound = false; // check need to gather all duplicate area names
  350. var duplicateAreaNames = rules.reduce(function (acc, r) {
  351. if (!r.params && selectorsEqual(r, rule)) {
  352. duplicatesFound = true;
  353. return r.duplicateAreaNames;
  354. }
  355. if (!duplicatesFound) {
  356. areaNames.forEach(function (name) {
  357. if (r.areas[name]) {
  358. acc.push(name);
  359. }
  360. });
  361. }
  362. return uniq(acc);
  363. }, []); // update grid-row/column-span values for areas with duplicate
  364. // area names. @see #1084 and #1146
  365. rules.forEach(function (r) {
  366. areaNames.forEach(function (name) {
  367. var area = r.areas[name];
  368. if (area && area.row.span !== areas[name].row.span) {
  369. areas[name].row.updateSpan = true;
  370. }
  371. if (area && area.column.span !== areas[name].column.span) {
  372. areas[name].column.updateSpan = true;
  373. }
  374. });
  375. });
  376. parsed[index].allAreas = uniq([].concat(allAreas, areaNames));
  377. parsed[index].rules.push({
  378. hasDuplicates: !hasNoDuplicates,
  379. params: media.params,
  380. selectors: rule.selectors,
  381. node: rule,
  382. duplicateAreaNames: duplicateAreaNames,
  383. areas: areas
  384. });
  385. } else {
  386. // index is NOT found, push the new item to the parsed array
  387. parsed.push({
  388. allAreas: areaNames,
  389. areasCount: 0,
  390. rules: [{
  391. hasDuplicates: false,
  392. duplicateRules: [],
  393. params: media.params,
  394. selectors: rule.selectors,
  395. node: rule,
  396. duplicateAreaNames: [],
  397. areas: areas
  398. }]
  399. });
  400. }
  401. return undefined;
  402. });
  403. return parsed;
  404. }
  405. /**
  406. * insert prefixed grid-area declarations
  407. * @param {Root} css css root
  408. * @param {Function} isDisabled check if the rule is disabled
  409. * @return {void}
  410. */
  411. function insertAreas(css, isDisabled) {
  412. // parse grid-template declarations
  413. var gridTemplatesData = parseGridTemplatesData(css); // return undefined if no declarations found
  414. if (gridTemplatesData.length === 0) {
  415. return undefined;
  416. } // we need to store the rules that we will insert later
  417. var rulesToInsert = {};
  418. css.walkDecls('grid-area', function (gridArea) {
  419. var gridAreaRule = gridArea.parent;
  420. var hasPrefixedRow = gridAreaRule.first.prop === '-ms-grid-row';
  421. var gridAreaMedia = getParentMedia(gridAreaRule);
  422. if (isDisabled(gridArea)) {
  423. return undefined;
  424. }
  425. var gridAreaRuleIndex = gridAreaMedia ? css.index(gridAreaMedia) : css.index(gridAreaRule);
  426. var value = gridArea.value; // found the data that matches grid-area identifier
  427. var data = gridTemplatesData.filter(function (d) {
  428. return d.allAreas.includes(value);
  429. })[0];
  430. if (!data) {
  431. return true;
  432. }
  433. var lastArea = data.allAreas[data.allAreas.length - 1];
  434. var selectorBySpace = list.space(gridAreaRule.selector);
  435. var selectorByComma = list.comma(gridAreaRule.selector);
  436. var selectorIsComplex = selectorBySpace.length > 1 && selectorBySpace.length > selectorByComma.length; // prevent doubling of prefixes
  437. if (hasPrefixedRow) {
  438. return false;
  439. } // create the empty object with the key as the last area name
  440. // e.g if we have templates with "a b c" values, "c" will be the last area
  441. if (!rulesToInsert[lastArea]) {
  442. rulesToInsert[lastArea] = {};
  443. } // walk through every grid-template rule data
  444. for (var _iterator2 = data.rules, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) {
  445. var _ref9;
  446. if (_isArray2) {
  447. if (_i2 >= _iterator2.length) break;
  448. _ref9 = _iterator2[_i2++];
  449. } else {
  450. _i2 = _iterator2.next();
  451. if (_i2.done) break;
  452. _ref9 = _i2.value;
  453. }
  454. var rule = _ref9;
  455. var area = rule.areas[value];
  456. var hasDuplicateName = rule.duplicateAreaNames.includes(value); // if we can't find the area name, update lastRule and continue
  457. if (!area) {
  458. var lastRuleIndex = css.index(rulesToInsert[lastArea].lastRule);
  459. if (gridAreaRuleIndex > lastRuleIndex) {
  460. rulesToInsert[lastArea].lastRule = gridAreaMedia || gridAreaRule;
  461. }
  462. continue;
  463. } // for grid-templates inside media rule we need to create empty
  464. // array to push prefixed grid-area rules later
  465. if (rule.params && !rulesToInsert[lastArea][rule.params]) {
  466. rulesToInsert[lastArea][rule.params] = [];
  467. }
  468. if ((!rule.hasDuplicates || !hasDuplicateName) && !rule.params) {
  469. // grid-template has no duplicates and not inside media rule
  470. getMSDecls(area, false, false).reverse().forEach(function (i) {
  471. return gridAreaRule.prepend(Object.assign(i, {
  472. raws: {
  473. between: gridArea.raws.between
  474. }
  475. }));
  476. });
  477. rulesToInsert[lastArea].lastRule = gridAreaRule;
  478. } else if (rule.hasDuplicates && !rule.params && !selectorIsComplex) {
  479. (function () {
  480. // grid-template has duplicates and not inside media rule
  481. var cloned = gridAreaRule.clone();
  482. cloned.removeAll();
  483. getMSDecls(area, area.row.updateSpan, area.column.updateSpan).reverse().forEach(function (i) {
  484. return cloned.prepend(Object.assign(i, {
  485. raws: {
  486. between: gridArea.raws.between
  487. }
  488. }));
  489. });
  490. cloned.selectors = changeDuplicateAreaSelectors(cloned.selectors, rule.selectors);
  491. if (rulesToInsert[lastArea].lastRule) {
  492. rulesToInsert[lastArea].lastRule.after(cloned);
  493. }
  494. rulesToInsert[lastArea].lastRule = cloned;
  495. })();
  496. } else if (rule.hasDuplicates && !rule.params && selectorIsComplex && gridAreaRule.selector.includes(rule.selectors[0])) {
  497. // grid-template has duplicates and not inside media rule
  498. // see the bottom of grid-template-areas test case for example
  499. gridAreaRule.walkDecls(/-ms-grid-(row|column)/, function (d) {
  500. return d.remove();
  501. });
  502. getMSDecls(area, area.row.updateSpan, area.column.updateSpan).reverse().forEach(function (i) {
  503. return gridAreaRule.prepend(Object.assign(i, {
  504. raws: {
  505. between: gridArea.raws.between
  506. }
  507. }));
  508. });
  509. } else if (rule.params) {
  510. (function () {
  511. // grid-template is inside media rule
  512. // if we're inside media rule, we need to store prefixed rules
  513. // inside rulesToInsert object to be able to preserve the order of media
  514. // rules and merge them easily
  515. var cloned = gridAreaRule.clone();
  516. cloned.removeAll();
  517. getMSDecls(area, area.row.updateSpan, area.column.updateSpan).reverse().forEach(function (i) {
  518. return cloned.prepend(Object.assign(i, {
  519. raws: {
  520. between: gridArea.raws.between
  521. }
  522. }));
  523. });
  524. if (rule.hasDuplicates && hasDuplicateName) {
  525. cloned.selectors = changeDuplicateAreaSelectors(cloned.selectors, rule.selectors);
  526. }
  527. cloned.raws = rule.node.raws;
  528. if (css.index(rule.node.parent) > gridAreaRuleIndex) {
  529. // append the prefixed rules right inside media rule
  530. // with grid-template
  531. rule.node.parent.append(cloned);
  532. } else {
  533. // store the rule to insert later
  534. rulesToInsert[lastArea][rule.params].push(cloned);
  535. }
  536. if (gridAreaMedia) {
  537. rulesToInsert[lastArea].lastRule = gridAreaMedia || gridAreaRule;
  538. }
  539. })();
  540. }
  541. }
  542. return undefined;
  543. }); // append stored rules inside the media rules
  544. Object.keys(rulesToInsert).forEach(function (area) {
  545. var data = rulesToInsert[area];
  546. var lastRule = data.lastRule;
  547. Object.keys(data).reverse().filter(function (p) {
  548. return p !== 'lastRule';
  549. }).forEach(function (params) {
  550. if (data[params].length > 0 && lastRule) {
  551. lastRule.after({
  552. name: 'media',
  553. params: params
  554. });
  555. lastRule.next().append(data[params]);
  556. }
  557. });
  558. });
  559. return undefined;
  560. }
  561. /**
  562. * Warn user if grid area identifiers are not found
  563. * @param {Object} areas
  564. * @param {Declaration} decl
  565. * @param {Result} result
  566. * @return {void}
  567. */
  568. function warnMissedAreas(areas, decl, result) {
  569. var missed = Object.keys(areas);
  570. decl.root().walkDecls('grid-area', function (gridArea) {
  571. missed = missed.filter(function (e) {
  572. return e !== gridArea.value;
  573. });
  574. });
  575. if (missed.length > 0) {
  576. decl.warn(result, 'Can not find grid areas: ' + missed.join(', '));
  577. }
  578. return undefined;
  579. }
  580. /**
  581. * compare selectors with grid-area rule and grid-template rule
  582. * show warning if grid-template selector is not found
  583. * (this function used for grid-area rule)
  584. * @param {Declaration} decl
  585. * @param {Result} result
  586. * @return {void}
  587. */
  588. function warnTemplateSelectorNotFound(decl, result) {
  589. var rule = decl.parent;
  590. var root = decl.root();
  591. var duplicatesFound = false; // slice selector array. Remove the last part (for comparison)
  592. var slicedSelectorArr = list.space(rule.selector).filter(function (str) {
  593. return str !== '>';
  594. }).slice(0, -1); // we need to compare only if selector is complex.
  595. // e.g '.grid-cell' is simple, but '.parent > .grid-cell' is complex
  596. if (slicedSelectorArr.length > 0) {
  597. var gridTemplateFound = false;
  598. var foundAreaSelector = null;
  599. root.walkDecls(/grid-template(-areas)?$/, function (d) {
  600. var parent = d.parent;
  601. var templateSelectors = parent.selectors;
  602. var _parseTemplate2 = parseTemplate({
  603. decl: d,
  604. gap: getGridGap(d)
  605. }),
  606. areas = _parseTemplate2.areas;
  607. var hasArea = areas[decl.value]; // find the the matching selectors
  608. for (var _iterator3 = templateSelectors, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) {
  609. var _ref10;
  610. if (_isArray3) {
  611. if (_i3 >= _iterator3.length) break;
  612. _ref10 = _iterator3[_i3++];
  613. } else {
  614. _i3 = _iterator3.next();
  615. if (_i3.done) break;
  616. _ref10 = _i3.value;
  617. }
  618. var tplSelector = _ref10;
  619. if (gridTemplateFound) {
  620. break;
  621. }
  622. var tplSelectorArr = list.space(tplSelector).filter(function (str) {
  623. return str !== '>';
  624. });
  625. gridTemplateFound = tplSelectorArr.every(function (item, idx) {
  626. return item === slicedSelectorArr[idx];
  627. });
  628. }
  629. if (gridTemplateFound || !hasArea) {
  630. return true;
  631. }
  632. if (!foundAreaSelector) {
  633. foundAreaSelector = parent.selector;
  634. } // if we found the duplicate area with different selector
  635. if (foundAreaSelector && foundAreaSelector !== parent.selector) {
  636. duplicatesFound = true;
  637. }
  638. return undefined;
  639. }); // warn user if we didn't find template
  640. if (!gridTemplateFound && duplicatesFound) {
  641. decl.warn(result, "Autoprefixer cannot find a grid-template " + ("containing the duplicate grid-area \"" + decl.value + "\" ") + ("with full selector matching: " + slicedSelectorArr.join(' ')));
  642. }
  643. }
  644. }
  645. /**
  646. * warn user if both grid-area and grid-(row|column)
  647. * declarations are present in the same rule
  648. * @param {Declaration} decl
  649. * @param {Result} result
  650. * @return {void}
  651. */
  652. function warnIfGridRowColumnExists(decl, result) {
  653. var rule = decl.parent;
  654. var decls = [];
  655. rule.walkDecls(/^grid-(row|column)/, function (d) {
  656. if (!/-end$/.test(d.prop) && !/^span/.test(d.value)) {
  657. decls.push(d);
  658. }
  659. });
  660. if (decls.length > 0) {
  661. decls.forEach(function (d) {
  662. d.warn(result, "You already have a grid-area declaration present in the rule. " + ("You should use either grid-area or " + d.prop + ", not both"));
  663. });
  664. }
  665. return undefined;
  666. } // Gap utils
  667. function getGridGap(decl) {
  668. var gap = {}; // try to find gap
  669. var testGap = /^(grid-)?((row|column)-)?gap$/;
  670. decl.parent.walkDecls(testGap, function (_ref11) {
  671. var prop = _ref11.prop,
  672. value = _ref11.value;
  673. if (/^(grid-)?gap$/.test(prop)) {
  674. var _parser$nodes = parser(value).nodes,
  675. _parser$nodes$ = _parser$nodes[0],
  676. row = _parser$nodes$ === void 0 ? {} : _parser$nodes$,
  677. _parser$nodes$2 = _parser$nodes[2],
  678. column = _parser$nodes$2 === void 0 ? {} : _parser$nodes$2;
  679. gap.row = row.value;
  680. gap.column = column.value || row.value;
  681. }
  682. if (/^(grid-)?row-gap$/.test(prop)) gap.row = value;
  683. if (/^(grid-)?column-gap$/.test(prop)) gap.column = value;
  684. });
  685. return gap;
  686. }
  687. /**
  688. * parse media parameters (for example 'min-width: 500px')
  689. * @param {String} params parameter to parse
  690. * @return {}
  691. */
  692. function parseMediaParams(params) {
  693. if (!params) {
  694. return false;
  695. }
  696. var parsed = parser(params);
  697. var prop;
  698. var value;
  699. parsed.walk(function (node) {
  700. if (node.type === 'word' && /min|max/g.test(node.value)) {
  701. prop = node.value;
  702. } else if (node.value.includes('px')) {
  703. value = parseInt(node.value.replace(/\D/g, ''));
  704. }
  705. });
  706. return [prop, value];
  707. }
  708. /**
  709. * Compare the selectors and decide if we
  710. * need to inherit gap from compared selector or not.
  711. * @type {String} selA
  712. * @type {String} selB
  713. * @return {Boolean}
  714. */
  715. function shouldInheritGap(selA, selB) {
  716. var result; // get arrays of selector split in 3-deep array
  717. var splitSelectorArrA = splitSelector(selA);
  718. var splitSelectorArrB = splitSelector(selB);
  719. if (splitSelectorArrA[0].length < splitSelectorArrB[0].length) {
  720. // abort if selectorA has lower descendant specificity then selectorB
  721. // (e.g '.grid' and '.hello .world .grid')
  722. return false;
  723. } else if (splitSelectorArrA[0].length > splitSelectorArrB[0].length) {
  724. // if selectorA has higher descendant specificity then selectorB
  725. // (e.g '.foo .bar .grid' and '.grid')
  726. var idx = splitSelectorArrA[0].reduce(function (res, _ref12, index) {
  727. var item = _ref12[0];
  728. var firstSelectorPart = splitSelectorArrB[0][0][0];
  729. if (item === firstSelectorPart) {
  730. return index;
  731. }
  732. return false;
  733. }, false);
  734. if (idx) {
  735. result = splitSelectorArrB[0].every(function (arr, index) {
  736. return arr.every(function (part, innerIndex) {
  737. return (// because selectorA has more space elements, we need to slice
  738. // selectorA array by 'idx' number to compare them
  739. splitSelectorArrA[0].slice(idx)[index][innerIndex] === part
  740. );
  741. });
  742. });
  743. }
  744. } else {
  745. // if selectorA has the same descendant specificity as selectorB
  746. // this condition covers cases such as: '.grid.foo.bar' and '.grid'
  747. result = splitSelectorArrB.some(function (byCommaArr) {
  748. return byCommaArr.every(function (bySpaceArr, index) {
  749. return bySpaceArr.every(function (part, innerIndex) {
  750. return splitSelectorArrA[0][index][innerIndex] === part;
  751. });
  752. });
  753. });
  754. }
  755. return result;
  756. }
  757. /**
  758. * inherit grid gap values from the closest rule above
  759. * with the same selector
  760. * @param {Declaration} decl
  761. * @param {Object} gap gap values
  762. * @return {Object | Boolean} return gap values or false (if not found)
  763. */
  764. function inheritGridGap(decl, gap) {
  765. var rule = decl.parent;
  766. var mediaRule = getParentMedia(rule);
  767. var root = rule.root(); // get an array of selector split in 3-deep array
  768. var splitSelectorArr = splitSelector(rule.selector); // abort if the rule already has gaps
  769. if (Object.keys(gap).length > 0) {
  770. return false;
  771. } // e.g ['min-width']
  772. var _parseMediaParams = parseMediaParams(mediaRule.params),
  773. prop = _parseMediaParams[0];
  774. var lastBySpace = splitSelectorArr[0]; // get escaped value from the selector
  775. // if we have '.grid-2.foo.bar' selector, will be '\.grid\-2'
  776. var escaped = escapeRegexp(lastBySpace[lastBySpace.length - 1][0]);
  777. var regexp = new RegExp("(" + escaped + "$)|(" + escaped + "[,.])"); // find the closest rule with the same selector
  778. var closestRuleGap;
  779. root.walkRules(regexp, function (r) {
  780. var gridGap; // abort if are checking the same rule
  781. if (rule.toString() === r.toString()) {
  782. return false;
  783. } // find grid-gap values
  784. r.walkDecls('grid-gap', function (d) {
  785. return gridGap = getGridGap(d);
  786. }); // skip rule without gaps
  787. if (!gridGap || Object.keys(gridGap).length === 0) {
  788. return true;
  789. } // skip rules that should not be inherited from
  790. if (!shouldInheritGap(rule.selector, r.selector)) {
  791. return true;
  792. }
  793. var media = getParentMedia(r);
  794. if (media) {
  795. // if we are inside media, we need to check that media props match
  796. // e.g ('min-width' === 'min-width')
  797. var propToCompare = parseMediaParams(media.params)[0];
  798. if (propToCompare === prop) {
  799. closestRuleGap = gridGap;
  800. return true;
  801. }
  802. } else {
  803. closestRuleGap = gridGap;
  804. return true;
  805. }
  806. return undefined;
  807. }); // if we find the closest gap object
  808. if (closestRuleGap && Object.keys(closestRuleGap).length > 0) {
  809. return closestRuleGap;
  810. }
  811. return false;
  812. }
  813. function warnGridGap(_ref13) {
  814. var gap = _ref13.gap,
  815. hasColumns = _ref13.hasColumns,
  816. decl = _ref13.decl,
  817. result = _ref13.result;
  818. var hasBothGaps = gap.row && gap.column;
  819. if (!hasColumns && (hasBothGaps || gap.column && !gap.row)) {
  820. delete gap.column;
  821. decl.warn(result, 'Can not impliment grid-gap without grid-tamplate-columns');
  822. }
  823. }
  824. /**
  825. * normalize the grid-template-rows/columns values
  826. * @param {String} str grid-template-rows/columns value
  827. * @return {Array} normalized array with values
  828. * @example
  829. * let normalized = normalizeRowColumn('1fr repeat(2, 20px 50px) 1fr')
  830. * normalized // <= ['1fr', '20px', '50px', '20px', '50px', '1fr']
  831. */
  832. function normalizeRowColumn(str) {
  833. var normalized = parser(str).nodes.reduce(function (result, node) {
  834. if (node.type === 'function' && node.value === 'repeat') {
  835. var key = 'count';
  836. var _node$nodes$reduce = node.nodes.reduce(function (acc, n) {
  837. if (n.type === 'word' && key === 'count') {
  838. acc[0] = Math.abs(parseInt(n.value));
  839. return acc;
  840. }
  841. if (n.type === 'div' && n.value === ',') {
  842. key = 'value';
  843. return acc;
  844. }
  845. if (key === 'value') {
  846. acc[1] += parser.stringify(n);
  847. }
  848. return acc;
  849. }, [0, '']),
  850. count = _node$nodes$reduce[0],
  851. value = _node$nodes$reduce[1];
  852. if (count) {
  853. for (var i = 0; i < count; i++) {
  854. result.push(value);
  855. }
  856. }
  857. return result;
  858. }
  859. if (node.type === 'space') {
  860. return result;
  861. }
  862. return result.concat(parser.stringify(node));
  863. }, []);
  864. return normalized;
  865. }
  866. /**
  867. * Autoplace grid items
  868. * @param {Declaration} decl
  869. * @param {Result} result
  870. * @param {Object} gap gap values
  871. * @param {String} autoflowValue grid-auto-flow value
  872. * @return {void}
  873. * @see https://github.com/postcss/autoprefixer/issues/1148
  874. */
  875. function autoplaceGridItems(decl, result, gap, autoflowValue) {
  876. if (autoflowValue === void 0) {
  877. autoflowValue = 'row';
  878. }
  879. var parent = decl.parent;
  880. var rowDecl = parent.nodes.find(function (i) {
  881. return i.prop === 'grid-template-rows';
  882. });
  883. var rows = normalizeRowColumn(rowDecl.value);
  884. var columns = normalizeRowColumn(decl.value); // Build array of area names with dummy values. If we have 3 columns and
  885. // 2 rows, filledRows will be equal to ['1 2 3', '4 5 6']
  886. var filledRows = rows.map(function (_, rowIndex) {
  887. return Array.from({
  888. length: columns.length
  889. }, function (v, k) {
  890. return k + rowIndex * columns.length + 1;
  891. }).join(' ');
  892. });
  893. var areas = parseGridAreas({
  894. rows: filledRows,
  895. gap: gap
  896. });
  897. var keys = Object.keys(areas);
  898. var items = keys.map(function (i) {
  899. return areas[i];
  900. }); // Change the order of cells if grid-auto-flow value is 'column'
  901. if (autoflowValue.includes('column')) {
  902. items = items.sort(function (a, b) {
  903. return a.column.start - b.column.start;
  904. });
  905. } // Insert new rules
  906. items.reverse().forEach(function (item, index) {
  907. var column = item.column,
  908. row = item.row;
  909. var nodeSelector = parent.selectors.map(function (sel) {
  910. return sel + (" > *:nth-child(" + (keys.length - index) + ")");
  911. }).join(', '); // create new rule
  912. var node = parent.clone().removeAll(); // change rule selector
  913. node.selector = nodeSelector; // insert prefixed row/column values
  914. node.append({
  915. prop: '-ms-grid-row',
  916. value: row.start
  917. });
  918. node.append({
  919. prop: '-ms-grid-column',
  920. value: column.start
  921. }); // insert rule
  922. parent.after(node);
  923. });
  924. return undefined;
  925. }
  926. module.exports = {
  927. parse: parse,
  928. translate: translate,
  929. parseTemplate: parseTemplate,
  930. parseGridAreas: parseGridAreas,
  931. warnMissedAreas: warnMissedAreas,
  932. insertAreas: insertAreas,
  933. insertDecl: insertDecl,
  934. prefixTrackProp: prefixTrackProp,
  935. prefixTrackValue: prefixTrackValue,
  936. getGridGap: getGridGap,
  937. warnGridGap: warnGridGap,
  938. warnTemplateSelectorNotFound: warnTemplateSelectorNotFound,
  939. warnIfGridRowColumnExists: warnIfGridRowColumnExists,
  940. inheritGridGap: inheritGridGap,
  941. autoplaceGridItems: autoplaceGridItems
  942. };