You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1149 lines
31 KiB

4 years ago
  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. }
  444. var lastRuleIsSet = false; // walk through every grid-template rule data
  445. for (var _iterator2 = data.rules, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) {
  446. var _ref9;
  447. if (_isArray2) {
  448. if (_i2 >= _iterator2.length) break;
  449. _ref9 = _iterator2[_i2++];
  450. } else {
  451. _i2 = _iterator2.next();
  452. if (_i2.done) break;
  453. _ref9 = _i2.value;
  454. }
  455. var rule = _ref9;
  456. var area = rule.areas[value];
  457. var hasDuplicateName = rule.duplicateAreaNames.includes(value); // if we can't find the area name, update lastRule and continue
  458. if (!area) {
  459. var lastRuleIndex = css.index(rulesToInsert[lastArea].lastRule);
  460. if (gridAreaRuleIndex > lastRuleIndex) {
  461. rulesToInsert[lastArea].lastRule = gridAreaMedia || gridAreaRule;
  462. }
  463. continue;
  464. } // for grid-templates inside media rule we need to create empty
  465. // array to push prefixed grid-area rules later
  466. if (rule.params && !rulesToInsert[lastArea][rule.params]) {
  467. rulesToInsert[lastArea][rule.params] = [];
  468. }
  469. if ((!rule.hasDuplicates || !hasDuplicateName) && !rule.params) {
  470. // grid-template has no duplicates and not inside media rule
  471. getMSDecls(area, false, false).reverse().forEach(function (i) {
  472. return gridAreaRule.prepend(Object.assign(i, {
  473. raws: {
  474. between: gridArea.raws.between
  475. }
  476. }));
  477. });
  478. rulesToInsert[lastArea].lastRule = gridAreaRule;
  479. lastRuleIsSet = true;
  480. } else if (rule.hasDuplicates && !rule.params && !selectorIsComplex) {
  481. (function () {
  482. // grid-template has duplicates and not inside media rule
  483. var cloned = gridAreaRule.clone();
  484. cloned.removeAll();
  485. getMSDecls(area, area.row.updateSpan, area.column.updateSpan).reverse().forEach(function (i) {
  486. return cloned.prepend(Object.assign(i, {
  487. raws: {
  488. between: gridArea.raws.between
  489. }
  490. }));
  491. });
  492. cloned.selectors = changeDuplicateAreaSelectors(cloned.selectors, rule.selectors);
  493. if (rulesToInsert[lastArea].lastRule) {
  494. rulesToInsert[lastArea].lastRule.after(cloned);
  495. }
  496. rulesToInsert[lastArea].lastRule = cloned;
  497. lastRuleIsSet = true;
  498. })();
  499. } else if (rule.hasDuplicates && !rule.params && selectorIsComplex && gridAreaRule.selector.includes(rule.selectors[0])) {
  500. // grid-template has duplicates and not inside media rule
  501. // and the selector is complex
  502. gridAreaRule.walkDecls(/-ms-grid-(row|column)/, function (d) {
  503. return d.remove();
  504. });
  505. getMSDecls(area, area.row.updateSpan, area.column.updateSpan).reverse().forEach(function (i) {
  506. return gridAreaRule.prepend(Object.assign(i, {
  507. raws: {
  508. between: gridArea.raws.between
  509. }
  510. }));
  511. });
  512. } else if (rule.params) {
  513. (function () {
  514. // grid-template is inside media rule
  515. // if we're inside media rule, we need to store prefixed rules
  516. // inside rulesToInsert object to be able to preserve the order of media
  517. // rules and merge them easily
  518. var cloned = gridAreaRule.clone();
  519. cloned.removeAll();
  520. getMSDecls(area, area.row.updateSpan, area.column.updateSpan).reverse().forEach(function (i) {
  521. return cloned.prepend(Object.assign(i, {
  522. raws: {
  523. between: gridArea.raws.between
  524. }
  525. }));
  526. });
  527. if (rule.hasDuplicates && hasDuplicateName) {
  528. cloned.selectors = changeDuplicateAreaSelectors(cloned.selectors, rule.selectors);
  529. }
  530. cloned.raws = rule.node.raws;
  531. if (css.index(rule.node.parent) > gridAreaRuleIndex) {
  532. // append the prefixed rules right inside media rule
  533. // with grid-template
  534. rule.node.parent.append(cloned);
  535. } else {
  536. // store the rule to insert later
  537. rulesToInsert[lastArea][rule.params].push(cloned);
  538. } // set new rule as last rule ONLY if we didn't set lastRule for
  539. // this grid-area before
  540. if (!lastRuleIsSet) {
  541. rulesToInsert[lastArea].lastRule = gridAreaMedia || gridAreaRule;
  542. }
  543. })();
  544. }
  545. }
  546. return undefined;
  547. }); // append stored rules inside the media rules
  548. Object.keys(rulesToInsert).forEach(function (area) {
  549. var data = rulesToInsert[area];
  550. var lastRule = data.lastRule;
  551. Object.keys(data).reverse().filter(function (p) {
  552. return p !== 'lastRule';
  553. }).forEach(function (params) {
  554. if (data[params].length > 0 && lastRule) {
  555. lastRule.after({
  556. name: 'media',
  557. params: params
  558. });
  559. lastRule.next().append(data[params]);
  560. }
  561. });
  562. });
  563. return undefined;
  564. }
  565. /**
  566. * Warn user if grid area identifiers are not found
  567. * @param {Object} areas
  568. * @param {Declaration} decl
  569. * @param {Result} result
  570. * @return {void}
  571. */
  572. function warnMissedAreas(areas, decl, result) {
  573. var missed = Object.keys(areas);
  574. decl.root().walkDecls('grid-area', function (gridArea) {
  575. missed = missed.filter(function (e) {
  576. return e !== gridArea.value;
  577. });
  578. });
  579. if (missed.length > 0) {
  580. decl.warn(result, 'Can not find grid areas: ' + missed.join(', '));
  581. }
  582. return undefined;
  583. }
  584. /**
  585. * compare selectors with grid-area rule and grid-template rule
  586. * show warning if grid-template selector is not found
  587. * (this function used for grid-area rule)
  588. * @param {Declaration} decl
  589. * @param {Result} result
  590. * @return {void}
  591. */
  592. function warnTemplateSelectorNotFound(decl, result) {
  593. var rule = decl.parent;
  594. var root = decl.root();
  595. var duplicatesFound = false; // slice selector array. Remove the last part (for comparison)
  596. var slicedSelectorArr = list.space(rule.selector).filter(function (str) {
  597. return str !== '>';
  598. }).slice(0, -1); // we need to compare only if selector is complex.
  599. // e.g '.grid-cell' is simple, but '.parent > .grid-cell' is complex
  600. if (slicedSelectorArr.length > 0) {
  601. var gridTemplateFound = false;
  602. var foundAreaSelector = null;
  603. root.walkDecls(/grid-template(-areas)?$/, function (d) {
  604. var parent = d.parent;
  605. var templateSelectors = parent.selectors;
  606. var _parseTemplate2 = parseTemplate({
  607. decl: d,
  608. gap: getGridGap(d)
  609. }),
  610. areas = _parseTemplate2.areas;
  611. var hasArea = areas[decl.value]; // find the the matching selectors
  612. for (var _iterator3 = templateSelectors, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) {
  613. var _ref10;
  614. if (_isArray3) {
  615. if (_i3 >= _iterator3.length) break;
  616. _ref10 = _iterator3[_i3++];
  617. } else {
  618. _i3 = _iterator3.next();
  619. if (_i3.done) break;
  620. _ref10 = _i3.value;
  621. }
  622. var tplSelector = _ref10;
  623. if (gridTemplateFound) {
  624. break;
  625. }
  626. var tplSelectorArr = list.space(tplSelector).filter(function (str) {
  627. return str !== '>';
  628. });
  629. gridTemplateFound = tplSelectorArr.every(function (item, idx) {
  630. return item === slicedSelectorArr[idx];
  631. });
  632. }
  633. if (gridTemplateFound || !hasArea) {
  634. return true;
  635. }
  636. if (!foundAreaSelector) {
  637. foundAreaSelector = parent.selector;
  638. } // if we found the duplicate area with different selector
  639. if (foundAreaSelector && foundAreaSelector !== parent.selector) {
  640. duplicatesFound = true;
  641. }
  642. return undefined;
  643. }); // warn user if we didn't find template
  644. if (!gridTemplateFound && duplicatesFound) {
  645. decl.warn(result, 'Autoprefixer cannot find a grid-template ' + ("containing the duplicate grid-area \"" + decl.value + "\" ") + ("with full selector matching: " + slicedSelectorArr.join(' ')));
  646. }
  647. }
  648. }
  649. /**
  650. * warn user if both grid-area and grid-(row|column)
  651. * declarations are present in the same rule
  652. * @param {Declaration} decl
  653. * @param {Result} result
  654. * @return {void}
  655. */
  656. function warnIfGridRowColumnExists(decl, result) {
  657. var rule = decl.parent;
  658. var decls = [];
  659. rule.walkDecls(/^grid-(row|column)/, function (d) {
  660. if (!d.prop.endsWith('-end') && !d.value.startsWith('span')) {
  661. decls.push(d);
  662. }
  663. });
  664. if (decls.length > 0) {
  665. decls.forEach(function (d) {
  666. 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"));
  667. });
  668. }
  669. return undefined;
  670. } // Gap utils
  671. function getGridGap(decl) {
  672. var gap = {}; // try to find gap
  673. var testGap = /^(grid-)?((row|column)-)?gap$/;
  674. decl.parent.walkDecls(testGap, function (_ref11) {
  675. var prop = _ref11.prop,
  676. value = _ref11.value;
  677. if (/^(grid-)?gap$/.test(prop)) {
  678. var _parser$nodes = parser(value).nodes,
  679. row = _parser$nodes[0],
  680. column = _parser$nodes[2];
  681. gap.row = row && parser.stringify(row);
  682. gap.column = column ? parser.stringify(column) : gap.row;
  683. }
  684. if (/^(grid-)?row-gap$/.test(prop)) gap.row = value;
  685. if (/^(grid-)?column-gap$/.test(prop)) gap.column = value;
  686. });
  687. return gap;
  688. }
  689. /**
  690. * parse media parameters (for example 'min-width: 500px')
  691. * @param {String} params parameter to parse
  692. * @return {}
  693. */
  694. function parseMediaParams(params) {
  695. if (!params) {
  696. return false;
  697. }
  698. var parsed = parser(params);
  699. var prop;
  700. var value;
  701. parsed.walk(function (node) {
  702. if (node.type === 'word' && /min|max/g.test(node.value)) {
  703. prop = node.value;
  704. } else if (node.value.includes('px')) {
  705. value = parseInt(node.value.replace(/\D/g, ''));
  706. }
  707. });
  708. return [prop, value];
  709. }
  710. /**
  711. * Compare the selectors and decide if we
  712. * need to inherit gap from compared selector or not.
  713. * @type {String} selA
  714. * @type {String} selB
  715. * @return {Boolean}
  716. */
  717. function shouldInheritGap(selA, selB) {
  718. var result; // get arrays of selector split in 3-deep array
  719. var splitSelectorArrA = splitSelector(selA);
  720. var splitSelectorArrB = splitSelector(selB);
  721. if (splitSelectorArrA[0].length < splitSelectorArrB[0].length) {
  722. // abort if selectorA has lower descendant specificity then selectorB
  723. // (e.g '.grid' and '.hello .world .grid')
  724. return false;
  725. } else if (splitSelectorArrA[0].length > splitSelectorArrB[0].length) {
  726. // if selectorA has higher descendant specificity then selectorB
  727. // (e.g '.foo .bar .grid' and '.grid')
  728. var idx = splitSelectorArrA[0].reduce(function (res, _ref12, index) {
  729. var item = _ref12[0];
  730. var firstSelectorPart = splitSelectorArrB[0][0][0];
  731. if (item === firstSelectorPart) {
  732. return index;
  733. }
  734. return false;
  735. }, false);
  736. if (idx) {
  737. result = splitSelectorArrB[0].every(function (arr, index) {
  738. return arr.every(function (part, innerIndex) {
  739. return (// because selectorA has more space elements, we need to slice
  740. // selectorA array by 'idx' number to compare them
  741. splitSelectorArrA[0].slice(idx)[index][innerIndex] === part
  742. );
  743. });
  744. });
  745. }
  746. } else {
  747. // if selectorA has the same descendant specificity as selectorB
  748. // this condition covers cases such as: '.grid.foo.bar' and '.grid'
  749. result = splitSelectorArrB.some(function (byCommaArr) {
  750. return byCommaArr.every(function (bySpaceArr, index) {
  751. return bySpaceArr.every(function (part, innerIndex) {
  752. return splitSelectorArrA[0][index][innerIndex] === part;
  753. });
  754. });
  755. });
  756. }
  757. return result;
  758. }
  759. /**
  760. * inherit grid gap values from the closest rule above
  761. * with the same selector
  762. * @param {Declaration} decl
  763. * @param {Object} gap gap values
  764. * @return {Object | Boolean} return gap values or false (if not found)
  765. */
  766. function inheritGridGap(decl, gap) {
  767. var rule = decl.parent;
  768. var mediaRule = getParentMedia(rule);
  769. var root = rule.root(); // get an array of selector split in 3-deep array
  770. var splitSelectorArr = splitSelector(rule.selector); // abort if the rule already has gaps
  771. if (Object.keys(gap).length > 0) {
  772. return false;
  773. } // e.g ['min-width']
  774. var _parseMediaParams = parseMediaParams(mediaRule.params),
  775. prop = _parseMediaParams[0];
  776. var lastBySpace = splitSelectorArr[0]; // get escaped value from the selector
  777. // if we have '.grid-2.foo.bar' selector, will be '\.grid\-2'
  778. var escaped = escapeRegexp(lastBySpace[lastBySpace.length - 1][0]);
  779. var regexp = new RegExp("(" + escaped + "$)|(" + escaped + "[,.])"); // find the closest rule with the same selector
  780. var closestRuleGap;
  781. root.walkRules(regexp, function (r) {
  782. var gridGap; // abort if are checking the same rule
  783. if (rule.toString() === r.toString()) {
  784. return false;
  785. } // find grid-gap values
  786. r.walkDecls('grid-gap', function (d) {
  787. return gridGap = getGridGap(d);
  788. }); // skip rule without gaps
  789. if (!gridGap || Object.keys(gridGap).length === 0) {
  790. return true;
  791. } // skip rules that should not be inherited from
  792. if (!shouldInheritGap(rule.selector, r.selector)) {
  793. return true;
  794. }
  795. var media = getParentMedia(r);
  796. if (media) {
  797. // if we are inside media, we need to check that media props match
  798. // e.g ('min-width' === 'min-width')
  799. var propToCompare = parseMediaParams(media.params)[0];
  800. if (propToCompare === prop) {
  801. closestRuleGap = gridGap;
  802. return true;
  803. }
  804. } else {
  805. closestRuleGap = gridGap;
  806. return true;
  807. }
  808. return undefined;
  809. }); // if we find the closest gap object
  810. if (closestRuleGap && Object.keys(closestRuleGap).length > 0) {
  811. return closestRuleGap;
  812. }
  813. return false;
  814. }
  815. function warnGridGap(_ref13) {
  816. var gap = _ref13.gap,
  817. hasColumns = _ref13.hasColumns,
  818. decl = _ref13.decl,
  819. result = _ref13.result;
  820. var hasBothGaps = gap.row && gap.column;
  821. if (!hasColumns && (hasBothGaps || gap.column && !gap.row)) {
  822. delete gap.column;
  823. decl.warn(result, 'Can not implement grid-gap without grid-template-columns');
  824. }
  825. }
  826. /**
  827. * normalize the grid-template-rows/columns values
  828. * @param {String} str grid-template-rows/columns value
  829. * @return {Array} normalized array with values
  830. * @example
  831. * let normalized = normalizeRowColumn('1fr repeat(2, 20px 50px) 1fr')
  832. * normalized // <= ['1fr', '20px', '50px', '20px', '50px', '1fr']
  833. */
  834. function normalizeRowColumn(str) {
  835. var normalized = parser(str).nodes.reduce(function (result, node) {
  836. if (node.type === 'function' && node.value === 'repeat') {
  837. var key = 'count';
  838. var _node$nodes$reduce = node.nodes.reduce(function (acc, n) {
  839. if (n.type === 'word' && key === 'count') {
  840. acc[0] = Math.abs(parseInt(n.value));
  841. return acc;
  842. }
  843. if (n.type === 'div' && n.value === ',') {
  844. key = 'value';
  845. return acc;
  846. }
  847. if (key === 'value') {
  848. acc[1] += parser.stringify(n);
  849. }
  850. return acc;
  851. }, [0, '']),
  852. count = _node$nodes$reduce[0],
  853. value = _node$nodes$reduce[1];
  854. if (count) {
  855. for (var i = 0; i < count; i++) {
  856. result.push(value);
  857. }
  858. }
  859. return result;
  860. }
  861. if (node.type === 'space') {
  862. return result;
  863. }
  864. return result.concat(parser.stringify(node));
  865. }, []);
  866. return normalized;
  867. }
  868. /**
  869. * Autoplace grid items
  870. * @param {Declaration} decl
  871. * @param {Result} result
  872. * @param {Object} gap gap values
  873. * @param {String} autoflowValue grid-auto-flow value
  874. * @return {void}
  875. * @see https://github.com/postcss/autoprefixer/issues/1148
  876. */
  877. function autoplaceGridItems(decl, result, gap, autoflowValue) {
  878. if (autoflowValue === void 0) {
  879. autoflowValue = 'row';
  880. }
  881. var parent = decl.parent;
  882. var rowDecl = parent.nodes.find(function (i) {
  883. return i.prop === 'grid-template-rows';
  884. });
  885. var rows = normalizeRowColumn(rowDecl.value);
  886. var columns = normalizeRowColumn(decl.value); // Build array of area names with dummy values. If we have 3 columns and
  887. // 2 rows, filledRows will be equal to ['1 2 3', '4 5 6']
  888. var filledRows = rows.map(function (_, rowIndex) {
  889. return Array.from({
  890. length: columns.length
  891. }, function (v, k) {
  892. return k + rowIndex * columns.length + 1;
  893. }).join(' ');
  894. });
  895. var areas = parseGridAreas({
  896. rows: filledRows,
  897. gap: gap
  898. });
  899. var keys = Object.keys(areas);
  900. var items = keys.map(function (i) {
  901. return areas[i];
  902. }); // Change the order of cells if grid-auto-flow value is 'column'
  903. if (autoflowValue.includes('column')) {
  904. items = items.sort(function (a, b) {
  905. return a.column.start - b.column.start;
  906. });
  907. } // Insert new rules
  908. items.reverse().forEach(function (item, index) {
  909. var column = item.column,
  910. row = item.row;
  911. var nodeSelector = parent.selectors.map(function (sel) {
  912. return sel + (" > *:nth-child(" + (keys.length - index) + ")");
  913. }).join(', '); // create new rule
  914. var node = parent.clone().removeAll(); // change rule selector
  915. node.selector = nodeSelector; // insert prefixed row/column values
  916. node.append({
  917. prop: '-ms-grid-row',
  918. value: row.start
  919. });
  920. node.append({
  921. prop: '-ms-grid-column',
  922. value: column.start
  923. }); // insert rule
  924. parent.after(node);
  925. });
  926. return undefined;
  927. }
  928. module.exports = {
  929. parse: parse,
  930. translate: translate,
  931. parseTemplate: parseTemplate,
  932. parseGridAreas: parseGridAreas,
  933. warnMissedAreas: warnMissedAreas,
  934. insertAreas: insertAreas,
  935. insertDecl: insertDecl,
  936. prefixTrackProp: prefixTrackProp,
  937. prefixTrackValue: prefixTrackValue,
  938. getGridGap: getGridGap,
  939. warnGridGap: warnGridGap,
  940. warnTemplateSelectorNotFound: warnTemplateSelectorNotFound,
  941. warnIfGridRowColumnExists: warnIfGridRowColumnExists,
  942. inheritGridGap: inheritGridGap,
  943. autoplaceGridItems: autoplaceGridItems
  944. };