Repository: microsoft/griffel (ts)

Issue:
Multiple selectors are not properly scoped
When writing multiple selectors separated by commas only the first selector is properly scoped with a class name.

[Here's a link to an annotated repro case in CodeSandbox](https://codesandbox.io/s/griffel-focus-within-tmfh2e?file=/src/App.tsx).

## Current Behavior

A Griffel styles like

```js
makeStyles({
  root: {
    ":active,:focus-within": {
      ...shorthands.borderColor("red")
    }
  }
});
```

Produces a selector like this in DevTools:

```
.f1mk9nzz:active, :focus-within {
    border-right-color: red;
}
```

Currently only the `:active` selector is scoped with a generated class name.

## Expected Behavior

Both the `:active` and `:focus-within` selectors should be scoped with the same generated class name. So we should see something like this in DevTools:

```
.f1mk9nzz:active, .f1mk9nzz:focus-within {
    border-right-color: red;
}
```

Alternatively, if this syntax is not supported I should get an error in VSCode explaining to me that I cannot write styles in this way.

### Notes

In Codesandbox I get an error (red squiggle under the selectors) when writing `":active,:focus-within"` indicating to me that perhaps this way of writing styles is not supported by Griffel. However, I don't get this error when writing styles like this in VS Code so I'm not certain. I'm using `griffel@1.0.3` in both Codesandbox and VSCode.





Pull request description:
Fix: Multiple selectors and RTL flipping
Fixes #105 

## Current Behavior

When writing styles with multiple selectors and rules that will be flipped for RTL only the first selector is properly scoped to the generated class name.

```js
import { makeStyles } from '@griffel/core';

export default makeStyles({
  root: {
    ':active,:focus-within': {
      paddingLeft: '10px'
    }
  },
});
```

Generates

```
.f14f5aie:active,
.f14f5aie:focus-within {
  padding-left: 10px;
}

.f1sheuf0:active,
// 💣💥`:focus-with` rule applies globally! 😱
:focus-within {
  padding-right: 10px;
}
```

## New Behavior

```js
import { makeStyles } from '@griffel/core';

export default makeStyles({
  root: {
    ':active,:focus-within': {
      paddingLeft: '10px'
    }
  },
});
```

Generates

```
.f14f5aie:active,
.f14f5aie:focus-within {
  padding-left: 10px;
}

.f1sheuf0:active,
// `:focus-within` is properly scoped 👍👍
.f1sheuf0:focus-within {
  padding-right: 10px;
}
```


Patch:
diff --git a/change/@griffel-core-94a0ec59-abfa-4d0f-a8a8-843026c57848.json b/change/@griffel-core-94a0ec59-abfa-4d0f-a8a8-843026c57848.json
new file mode 100644
index 0000000..c5fd055
--- /dev/null
+++ b/change/@griffel-core-94a0ec59-abfa-4d0f-a8a8-843026c57848.json
@@ -0,0 +1,7 @@
+{
+  "type": "patch",
+  "comment": "fix issue with comma-separated selectors and RTL",
+  "packageName": "@griffel/core",
+  "email": "seanmonahan@microsoft.com",
+  "dependentChangeType": "patch"
+}
diff --git a/change/@griffel-core-ad30b8f8-c6c7-481e-be0d-2e8268b29fdd.json b/change/@griffel-core-ad30b8f8-c6c7-481e-be0d-2e8268b29fdd.json
new file mode 100644
index 0000000..1e841dd
--- /dev/null
+++ b/change/@griffel-core-ad30b8f8-c6c7-481e-be0d-2e8268b29fdd.json
@@ -0,0 +1,7 @@
+{
+  "type": "minor",
+  "comment": "feat: add shorthand function for outline",
+  "packageName": "@griffel/core",
+  "email": "bsunderhus@microsoft.com",
+  "dependentChangeType": "patch"
+}
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 4eab908..d432dfc 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -15,6 +15,7 @@ import {
   padding,
   overflow,
   inset,
+  outline,
 } from './shorthands/index';
 
 export const shorthands = {
@@ -32,6 +33,7 @@ export const shorthands = {
   padding,
   overflow,
   inset,
+  outline,
 };
 
 export { createDOMRenderer } from './renderer/createDOMRenderer';
diff --git a/packages/core/src/runtime/compileCSS.ts b/packages/core/src/runtime/compileCSS.ts
index 11adaeb..ab16ded 100644
--- a/packages/core/src/runtime/compileCSS.ts
+++ b/packages/core/src/runtime/compileCSS.ts
@@ -102,7 +102,7 @@ export function compileCSS(options: CompileCSSOptions): [string /* ltr definitio
     cssRule = `${classNameSelector}{${normalizedPseudo} ${cssDeclaration}};`;
 
     if (rtlProperty) {
-      cssRule = `${cssRule}; ${rtlClassNameSelector}${normalizedPseudo} ${rtlCSSDeclaration};`;
+      cssRule = `${cssRule}; ${rtlClassNameSelector}{${normalizedPseudo} ${rtlCSSDeclaration}};`;
     }
   }
 
diff --git a/packages/core/src/shorthands/index.ts b/packages/core/src/shorthands/index.ts
index b398d95..58d84c8 100644
--- a/packages/core/src/shorthands/index.ts
+++ b/packages/core/src/shorthands/index.ts
@@ -12,3 +12,4 @@ export { margin } from './margin';
 export { padding } from './padding';
 export { overflow } from './overflow';
 export { inset } from './inset';
+export { outline } from './outline';
diff --git a/packages/core/src/shorthands/outline.ts b/packages/core/src/shorthands/outline.ts
new file mode 100644
index 0000000..8f23efa
--- /dev/null
+++ b/packages/core/src/shorthands/outline.ts
@@ -0,0 +1,31 @@
+import type { GriffelStylesStrictCSSObject } from '../types';
+import { OutlineColorInput, OutlineStyleInput, OutlineWidthInput } from './types';
+
+type OutlineStyle = Pick<GriffelStylesStrictCSSObject, 'outlineColor' | 'outlineStyle' | 'outlineWidth'>;
+
+export function outline(width: OutlineWidthInput): OutlineStyle;
+export function outline(width: OutlineWidthInput, style: OutlineStyleInput): OutlineStyle;
+export function outline(width: OutlineWidthInput, style: OutlineStyleInput, color: OutlineColorInput): OutlineStyle;
+
+/**
+ * A function that implements expansion for "outline", it's simplified - check usage examples.
+ *
+ * @example
+ *  outline('2px')
+ *  outline('2px', 'solid')
+ *  outline('2px', 'solid', 'red')
+ *
+ * See https://developer.mozilla.org/en-US/docs/Web/CSS/outline
+ *
+ */
+export function outline(
+  outlineWidth: OutlineWidthInput,
+  outlineStyle?: OutlineStyleInput,
+  outlineColor?: OutlineColorInput,
+): OutlineStyle {
+  return {
+    outlineWidth,
+    ...(outlineStyle && { outlineStyle }),
+    ...(outlineColor && { outlineColor }),
+  };
+}
diff --git a/packages/core/src/shorthands/types.ts b/packages/core/src/shorthands/types.ts
index 2ac2eb6..ee8b706 100644
--- a/packages/core/src/shorthands/types.ts
+++ b/packages/core/src/shorthands/types.ts
@@ -5,6 +5,10 @@ export type BorderWidthInput = ValueOrArray<CSS.Property.BorderWidth<GriffelStyl
 export type BorderStyleInput = ValueOrArray<CSS.Property.BorderStyle>;
 export type BorderColorInput = ValueOrArray<CSS.Property.BorderColor>;
 
+export type OutlineWidthInput = ValueOrArray<CSS.Property.OutlineWidth<GriffelStylesCSSValue>>;
+export type OutlineStyleInput = ValueOrArray<CSS.Property.OutlineStyle>;
+export type OutlineColorInput = ValueOrArray<CSS.Property.OutlineColor>;
+
 export type BorderRadiusInput = ValueOrArray<CSS.Property.BorderRadius<GriffelStylesCSSValue>>;
 
 export type GapInput = ValueOrArray<CSS.Property.Gap<GriffelStylesCSSValue>>;


Test patch:
diff --git a/packages/core/src/runtime/resolveStyleRules.test.ts b/packages/core/src/runtime/resolveStyleRules.test.ts
index 3ceaa78..403698b 100644
--- a/packages/core/src/runtime/resolveStyleRules.test.ts
+++ b/packages/core/src/runtime/resolveStyleRules.test.ts
@@ -327,6 +327,25 @@ describe('resolveStyleRules', () => {
       `);
     });
 
+    it('handles comma-separated selectors', () => {
+      expect(
+        resolveStyleRules({
+          ':active,:focus-within': {
+            paddingLeft: '10px',
+          },
+        }),
+      ).toMatchInlineSnapshot(`
+        .f14f5aie:active,
+        .f14f5aie:focus-within {
+          padding-left: 10px;
+        }
+        .f1sheuf0:active,
+        .f1sheuf0:focus-within {
+          padding-right: 10px;
+        }
+      `);
+    });
+
     it('handles media queries', () => {
       expect(
         resolveStyleRules({
diff --git a/packages/core/src/shorthands/outline.test.ts b/packages/core/src/shorthands/outline.test.ts
new file mode 100644
index 0000000..bcaaadf
--- /dev/null
+++ b/packages/core/src/shorthands/outline.test.ts
@@ -0,0 +1,31 @@
+import { outline } from './outline';
+describe('outline', () => {
+  it('for a given width', () => {
+    expect(outline('2px')).toEqual({
+      outlineWidth: '2px',
+    });
+  });
+
+  it('for a given width and style', () => {
+    expect(outline('2px', 'solid')).toEqual({
+      outlineWidth: '2px',
+      outlineStyle: 'solid',
+    });
+  });
+
+  it('for a given width, style and color', () => {
+    expect(outline('2px', 'solid', 'red')).toEqual({
+      outlineWidth: '2px',
+      outlineStyle: 'solid',
+      outlineColor: 'red',
+    });
+  });
+
+  it('for fallback value arrays', () => {
+    expect(outline(['2px', '4px'], ['solid', 'dashed'], ['red', 'green'])).toEqual({
+      outlineColor: ['red', 'green'],
+      outlineStyle: ['solid', 'dashed'],
+      outlineWidth: ['2px', '4px'],
+    });
+  });
+});