Skip to content

Commit

Permalink
Add nested option to nest media queries
Browse files Browse the repository at this point in the history
  • Loading branch information
vis97c committed Jun 1, 2023
1 parent f546e59 commit 3b4691b
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 20 deletions.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,18 @@ postcss([

This will only merge the ones that are not nested within selectors

### Nest media queries

Nest compatible top level media queries.

```js
postcss([
sortMediaQueries({
nested: true,
})
]).process(css);
```

---

## Changelog
Expand Down
145 changes: 125 additions & 20 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
function sortAtRules(queries, sort, sortCSSmq) {
if (typeof sort !== "function") {
sort = sort === "desktop-first" ? sortCSSmq.desktopFirst : sortCSSmq;
function getParams(query) {
return query.split('and').map(q => q.trim())
}

function sortParams(params) {
const hasNumber = (param) => /\d/.test(param)
const sort = (a, b) => {
if (hasNumber(a)) return 1;
else if (hasNumber(b)) return -1;
return 0;
}
const paramsArr = getParams(params).sort(sort);

return queries.sort(sort);
return paramsArr.join(' and ');
}

module.exports = (opts = {}) => {
Expand All @@ -13,6 +21,7 @@ module.exports = (opts = {}) => {
configuration: false,
onlyTopLevel: false,
mergeAtRules: false,
nested: false,
},
opts
);
Expand All @@ -26,6 +35,93 @@ module.exports = (opts = {}) => {
postcssPlugin: "postcss-sort-media-queries",
OnceExit(root, { AtRule }) {

/**
* Sort atRules
* @param {object} atRulesObject object with atRule params as keys
* @returns Array of atRules
*/
function sortAtRules(atRulesObject) {
let sort = opts.sort;
const queries = Object.keys(atRulesObject);

if (typeof sort !== "function") {
sort = sort === "desktop-first" ? sortCSSmq.desktopFirst : sortCSSmq;
}

return queries.sort(sort).map((query) => atRulesObject[query]);
}

/**
* Nest atRules
*
* @param {array} localAtRules Array of atRules
* @returns Array of nested atRules
*/
function recursivelyNestAtRules(localAtRules) {
let i = 0;

while (i < localAtRules.length) {
const queryParams = getParams(localAtRules[i].params);
const query = queryParams.shift();
let atRules = {};

if (queryParams.length) {
localAtRules[i] = new AtRule({
name: localAtRules[i].name,
params: query,
nodes: recursivelyNestAtRules([
new AtRule({
name: localAtRules[i].name,
params: queryParams.join(' and '),
nodes: localAtRules[i].nodes,
})
]),
});

localAtRules = sortAtRules(localAtRules.reduce((acc, current) => {
return Object.assign(acc, { [current.params]: current });
}, {}));
} else {
localAtRules.forEach(({ params, name, nodes }) => {
if (params.startsWith(query) && params !== query) {
const newQuery = params.split(`${query} and`)[1].trim();
atRules[newQuery] = new AtRule({
name: name,
params: newQuery,
nodes: nodes,
});
}
});

if (Object.getOwnPropertyNames(atRules).length) {
atRules = sortAtRules(atRules);

recursivelyNestAtRules(atRules).forEach((atRule) => {
const existingAtRuleIndex = localAtRules[i].nodes.findIndex(({params}) => params === atRule.params)
if (existingAtRuleIndex > -1) {
if (localAtRules[i].nodes[existingAtRuleIndex].nodes) {
localAtRules[i].nodes[existingAtRuleIndex].nodes.forEach((node) => {
atRule.append(node.clone());
});
}

localAtRules[i].nodes[existingAtRuleIndex].remove();
}
localAtRules[i].append(atRule);
});

localAtRules = localAtRules.filter(({ params }) => {
return !params.startsWith(query) || params === query;
});
}

i++;
}
}

return localAtRules;
}

/**
* Recursively merge media queries & layer (optionally)
*
Expand All @@ -35,15 +131,15 @@ module.exports = (opts = {}) => {
function recursivelyMergeAtRules(localRoot, match) {
let atRules = {};

localRoot.walkAtRules(match || /(media|container)/mi, (atRule) => {
const query = atRule.params;
localRoot.walkAtRules(match || /(media|container)/im, (atRule) => {
const query = sortParams(atRule.params);

if (!match && opts.onlyTopLevel && atRule.parent.type !== "root") return;

if (!atRules[query]) {
atRules[query] = new AtRule({
name: atRule.name,
params: atRule.params,
params: query,
});
}

Expand All @@ -61,27 +157,36 @@ module.exports = (opts = {}) => {
return atRules;
}

const atRules = recursivelyMergeAtRules(root);
let atRules = recursivelyMergeAtRules(root);

if (opts.mergeAtRules) {
const atRuleRegex = /(layer|scope|supports)/mi;
const atRuleRegex = /(layer|scope|supports)/im;
const rootAtRules = recursivelyMergeAtRules(root, atRuleRegex);
Object.keys(rootAtRules).reverse().forEach(nestedAtRuleKey => {
root.prepend(rootAtRules[nestedAtRuleKey])
Object.keys(rootAtRules)
.reverse()
.forEach((nestedAtRuleKey) => {
root.prepend(rootAtRules[nestedAtRuleKey]);
});

Object.keys(atRules).forEach((query) => {
const nestedAtRules = recursivelyMergeAtRules(
atRules[query],
atRuleRegex
);
Object.keys(nestedAtRules)
.reverse()
.forEach((nestedAtRuleKey) => {
atRules[query].prepend(nestedAtRules[nestedAtRuleKey]);
});
});

Object.keys(atRules).forEach(query => {
const nestedAtRules = recursivelyMergeAtRules(atRules[query], atRuleRegex);
Object.keys(nestedAtRules).reverse().forEach(nestedAtRuleKey => {
atRules[query].prepend(nestedAtRules[nestedAtRuleKey])
})
})
}

if (atRules) {
let sortedAtRules = sortAtRules(Object.keys(atRules), opts.sort, sortCSSmq).map(query => atRules[query]);
atRules = sortAtRules(atRules);

if (opts.nested) atRules = recursivelyNestAtRules(atRules);

sortedAtRules.forEach((atRule) => {
atRules.forEach((atRule) => {
root.append(atRule);
});
}
Expand Down

0 comments on commit 3b4691b

Please sign in to comment.