From 133b9843f01c5a086656458f587b5a07d9353334 Mon Sep 17 00:00:00 2001 From: Wahid Rizka Fathurrohman Date: Sun, 18 Oct 2026 15:21:17 +0700 Subject: [PATCH] Keep declarations after nested rules inside at-rules Inside a bubbled at-rule, all declarations were moved into one rule before the nested rules, so `.a { @media x { color: red; & { color: blue } color: green } }` made the element blue, while Sass and native CSS nesting keep the order and make it green. Outside at-rules, declarations after a nested rule already get their own rule (#92), so do the same inside. --- index.js | 19 ++++++++++++++----- index.test.js | 11 +++++++++++ 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 81a6f6d..e9125de 100644 --- a/index.js +++ b/index.js @@ -178,11 +178,17 @@ function breakOut(child, currentNode) { function createFnAtruleChilds(bubble) { return function atruleChilds(rule, atrule, bubbling, mergeSels = bubbling) { let children = [] + let groups = [] atrule.each(child => { if (child.type === 'rule' && bubbling) { if (mergeSels) { child.selectors = mergeSelectors(rule, child) } + // Declarations after a nested rule stay after it, like outside at-rules + if (children.length) { + groups.push(children) + children = [] + } } else if (child.type === 'atrule' && child.nodes) { if (bubble[child.name]) { atruleChilds(rule, child, mergeSels) @@ -193,12 +199,15 @@ function createFnAtruleChilds(bubble) { children.push(child) } }) - if (bubbling && children.length) { - let clone = rule.clone({ nodes: [] }) - for (let child of children) { - clone.append(child) + if (children.length) groups.push(children) + if (bubbling) { + for (let group of groups) { + let clone = rule.clone({ nodes: [] }) + group[0].before(clone) + for (let child of group) { + clone.append(child) + } } - atrule.prepend(clone) } } } diff --git a/index.test.js b/index.test.js index 0d219f3..a5ab0fe 100644 --- a/index.test.js +++ b/index.test.js @@ -709,6 +709,17 @@ test('copies rule for declarations after nested rule and before at-rule', () => ) }) +test('copies rule for declarations after nested rule inside at-rule', () => { + run( + 'a { @media screen { a: 1; &b { b: 2 } c: 1; &c { d: 5 } e: 6 } }', + '@media screen { a { a: 1 } ab { b: 2 } a { c: 1 } ac { d: 5 } a { e: 6 } }' + ) + run( + 'a { @supports (a: 1) { &b { b: 2 } c: 1 } }', + '@supports (a: 1) { ab { b: 2 } a { c: 1 } }' + ) +}) + test('does not replace ampersand inside string', () => { run( 'div { &[data-category="sound & vision"] {} }',