Repository: ProjectEvergreen/greenwood (js)

Issue:
support all CSS function pseudo classes / elements for CSS optimizing
## Summary
Observed as part of #1106 / #1107, that I don't think we're covering all possible ["function" based pseudo classes and elements](https://developer.mozilla.org/en-US/docs/Web/CSS/) 😬 

## Details
We should audit for all of them, but here are a few I think we're missing, or at least want to make sure we're not overlooking are:

### Classes
- `dir()`
- `where()`

### Elements
- `::part()`
- `::slotted()`

Pull request description:
enhancement/issue 1178 support all remaining standard pseudo-class and element selectors for CSS bundling
<!--
## Submitting a Pull Request
We love contributions and appreciate any help you can offer!
-->

## Related Issue
resolves #1178 

## Summary of Changes
1. Adds support for bundling these missing [pseudo-classes](https://developer.mozilla.org/en-US/docs/Web/CSS/)
    - `dir`
    - `host`
    - `where`
1. Adds support for bundling these missing [pseudo-elements](https://developer.mozilla.org/en-US/docs/Web/CSS/)
    - `::highlight`
    - `::part`
    - `::slotted`

## TODO
1. [x] bundling gets `:host { }` and `:host() { }` confused and adds unexpected parens `()`
    ```css
    :host(){--primary-color:#16f;--secondary-color:#ff7;}
    :host{--primary-color:#16f;--secondary-color:#ff7;}
    ```
    
----

> Opened a discussion about potentially adopting Lightning CSS in a 2.0 version - https://github.com/ProjectEvergreen/greenwood/discussions/1238

Patch:
diff --git a/packages/cli/src/plugins/resource/plugin-standard-css.js b/packages/cli/src/plugins/resource/plugin-standard-css.js
index a7dfe1a6..b0cdbcb2 100644
--- a/packages/cli/src/plugins/resource/plugin-standard-css.js
+++ b/packages/cli/src/plugins/resource/plugin-standard-css.js
@@ -21,7 +21,7 @@ function bundleCss(body, url, compilation) {
 
   walk(ast, {
     enter: function (node, item) { // eslint-disable-line complexity
-      const { type, name, value } = node;
+      const { type, name, value, children } = node;
 
       if ((type === 'String' || type === 'Url') && this.atrulePrelude && this.atrule.name === 'import') {
         const { value } = node;
@@ -83,16 +83,35 @@ function bundleCss(body, url, compilation) {
       } else if (type === 'PseudoClassSelector') {
         optimizedCss += `:${name}`;
 
+        if (children) {
+          switch (name) {
+
+            case 'dir':
+            case 'host':
+            case 'is':
+            case 'has':
+            case 'lang':
+            case 'not':
+            case 'nth-child':
+            case 'nth-last-child':
+            case 'nth-of-type':
+            case 'nth-last-of-type':
+            case 'where':
+              optimizedCss += '(';
+              break;
+            default:
+              break;
+
+          }
+        }
+      } else if (type === 'PseudoElementSelector') {
+        optimizedCss += `::${name}`;
+
         switch (name) {
 
-          case 'is':
-          case 'has':
-          case 'lang':
-          case 'not':
-          case 'nth-child':
-          case 'nth-last-child':
-          case 'nth-of-type':
-          case 'nth-last-of-type':
+          case 'highlight':
+          case 'part':
+          case 'slotted':
             optimizedCss += '(';
             break;
           default:
@@ -192,16 +211,34 @@ function bundleCss(body, url, compilation) {
           optimizedCss += ')';
           break;
         case 'PseudoClassSelector':
+          if (node.children) {
+            switch (node.name) {
+
+              case 'dir':
+              case 'host':
+              case 'is':
+              case 'has':
+              case 'lang':
+              case 'not':
+              case 'nth-child':
+              case 'nth-last-child':
+              case 'nth-last-of-type':
+              case 'nth-of-type':
+              case 'where':
+                optimizedCss += ')';
+                break;
+              default:
+                break;
+
+            }
+          }
+          break;
+        case 'PseudoElementSelector':
           switch (node.name) {
 
-            case 'is':
-            case 'has':
-            case 'lang':
-            case 'not':
-            case 'nth-child':
-            case 'nth-last-child':
-            case 'nth-last-of-type':
-            case 'nth-of-type':
+            case 'highlight':
+            case 'part':
+            case 'slotted':
               optimizedCss += ')';
               break;
             default:


Test patch:
diff --git a/packages/cli/test/cases/build.config.optimization-default/fixtures/expected.css b/packages/cli/test/cases/build.config.optimization-default/fixtures/expected.css
index 2c7e2eff..bdaab670 100644
--- a/packages/cli/test/cases/build.config.optimization-default/fixtures/expected.css
+++ b/packages/cli/test/cases/build.config.optimization-default/fixtures/expected.css
@@ -65,3 +65,17 @@ h1:has(+h2){margin:0 0 0.25rem 0}
 h1{background-image:url('/foo/bar.baz')}
 
 .has-success{background-image:url('data:image/svg+xml;...')}
+
+:where(html){--size-000:-.5rem}
+
+:dir(rtl){background-color:red}
+
+:host(h1){color:red}
+
+::slotted(.content){background-color:aqua}
+
+h2 ::slotted(span){background:silver}
+
+tabbed-custom-element::part(tab){color:#0c0dcc;border-bottom:transparent solid 2px;}
+
+::highlight(rainbow-color-1){color:#ad26ad;text-decoration:underline;}
\ No newline at end of file
diff --git a/packages/cli/test/cases/build.config.optimization-default/src/styles/main.css b/packages/cli/test/cases/build.config.optimization-default/src/styles/main.css
index 8ebfcebf..b3173560 100644
--- a/packages/cli/test/cases/build.config.optimization-default/src/styles/main.css
+++ b/packages/cli/test/cases/build.config.optimization-default/src/styles/main.css
@@ -132,7 +132,6 @@ h1:has(+ h2) {
   color: green;
 }
 
-
 .snippet {
   margin: var(--size-4) 0;
   padding: 0 var(--size-4);
@@ -142,6 +141,36 @@ h1 {
   background-image: url('/foo/bar.baz');
 }
 
-.has-success{
+.has-success {
   background-image: url("data:image/svg+xml;...");
+}
+
+:where(html) {
+  --size-000: -.5rem;
+}
+
+:dir(rtl) {
+  background-color: red;
+}
+
+:host(h1) {
+  color: red;
+}
+
+::slotted(.content) {
+  background-color: aqua;
+}
+
+h2 ::slotted(span) {
+  background: silver;
+}
+
+tabbed-custom-element::part(tab) {
+  color: #0c0dcc;
+  border-bottom: transparent solid 2px;
+}
+
+::highlight(rainbow-color-1) {
+  color: #ad26ad;
+  text-decoration: underline;
 }
\ No newline at end of file