From 94d7aa09f6113757b12ec343842343c25bce63de Mon Sep 17 00:00:00 2001 From: Ryan Date: Sun, 1 Jan 2023 22:32:40 -0500 Subject: [PATCH] Update CssPrefixer to support prefixing with :host and :host-context functional pseudo classes --- src/css.js | 97 +++++++++++++++++++++++++++++++++++++------------ test/cssTest.js | 24 +++++++++--- 2 files changed, 92 insertions(+), 29 deletions(-) diff --git a/src/css.js b/src/css.js index 3a40307..91ed9cf 100644 --- a/src/css.js +++ b/src/css.js @@ -27,12 +27,6 @@ class CssPrefixer { }) } - shouldSkipPseudoClass(name) { - return { - "host-context": true, - }[name]; - } - process(cssString) { let ast = this.parse(cssString); @@ -42,27 +36,82 @@ class CssPrefixer { visit: "Selector", enter: (node, item, list) => { let first = node.children.first; - if(first.type === "PseudoClassSelector" && this.shouldSkipPseudoClass(first.name)) { - // Skip processing some pseudo classes - } else { - if(skipLevel > 0 || first.type === "TypeSelector" && (first.name === "from" || first.name === "to")) { - // do nothing - } else { - if(first.type === "PseudoClassSelector" && first.name === "host") { - // replace :host with prefix class - node.children.shift(); - } else { - node.children.prepend(list.createItem({ - type: "Combinator", - name: " " - })); + + const shouldSkip = + skipLevel > 0 || + (first.type === "TypeSelector" && first.name === "from") || + first.name === "to"; + + if (shouldSkip) { + // do nothing + } else if ( + first.type === "PseudoClassSelector" && + (first.name === "host" || first.name === "host-context") + ) { + // Transform :host and :host-context pseudo classes to + // use the prefix class + node.children.shift(); + + const pseudoClassParamChildren = first.children ? first.children : null; + + if (first.name === "host") { + // Replace :host with the prefix class + if (pseudoClassParamChildren) { + // Any param children of a :host() functional pseudo class should be moved up to + // be directly after the prefix class + // ie, :host(.foo) -> .prefix.foo + node.children.prependList( + // :host can only accept one param, so we can safely use the first child + pseudoClassParamChildren.first.children + ); } + node.children.prepend( + list.createItem({ + type: "ClassSelector", + name: this.prefix, + }) + ); + } else if (first.name === "host-context") { + // Replace :host-context with the prefix class and + // place any param children appropriately before the prefix class + node.children.prepend( + list.createItem({ + type: "ClassSelector", + name: this.prefix, + }) + ); - node.children.prepend(list.createItem({ - type: "ClassSelector", - name: this.prefix - })); + if (pseudoClassParamChildren) { + // Any param children of a :host-context() functional pseudo class should be moved up to + // be parents before the prefix class + // ie, :host-context(.foo) div -> .foo .prefix div + node.children.prepend( + list.createItem({ + type: "Combinator", + name: " ", + }) + ); + node.children.prependList( + // :host-context can only accept one param, so we can safely use the first child + pseudoClassParamChildren.first.children + ); + } } + } else { + // Prepand the prefix class in front of all selectors + // which don't include :host or :host-context + node.children.prepend( + list.createItem({ + type: "Combinator", + name: " ", + }) + ); + node.children.prepend( + list.createItem({ + type: "ClassSelector", + name: this.prefix, + }) + ); } node.children.forEach((node, item, list) => { diff --git a/test/cssTest.js b/test/cssTest.js index 0f7f2d7..a25b593 100644 --- a/test/cssTest.js +++ b/test/cssTest.js @@ -69,11 +69,25 @@ test("Pseudo classes", t => { t.is(c.process(`:host:not(p) div {}`), `.my-prefix:not(p) div{}`); t.is(c.process(`:host.footer div {}`), `.my-prefix.footer div{}`); // same as :host(.footer) - // TODO :host(.footer) should be `.my-prefix.footer` but we can use `:host.footer` for now - t.is(c.process(`:host(.footer) div {}`), `.my-prefix div{}`); - - // TODO host-context(html body) should be `html body .my-prefix` - t.is(c.process(`:host-context(html) div {}`), `:host-context(html) div{}`); + t.is(c.process(`:host(.footer) div {}`), `.my-prefix.footer div{}`); + t.is( + c.process(`:host(.footer:not([lang])) .link {}`), + `.my-prefix.footer:not([lang]) .link{}` + ); + t.is( + c.process(`:host(:is(div, span)) {}`), + `.my-prefix:is(div,span){}` + ); + + t.is(c.process(`:host-context(html) div {}`), `html .my-prefix div{}`); + t.is( + c.process(`:host-context(html body.dark-theme) div {}`), + `html body.dark-theme .my-prefix div{}` + ); + t.is( + c.process(`:host-context(html body:is(.dark-theme, .light-theme)) div {}`), + `html body:is(.dark-theme,.light-theme) .my-prefix div{}` + ); }); test("Pseudo elements", t => {