diff --git a/change/@fluentui-make-styles-e8e5cc37-473f-49b4-9102-50c88f7b022e.json b/change/@fluentui-make-styles-e8e5cc37-473f-49b4-9102-50c88f7b022e.json new file mode 100644 index 0000000000000..597f004b9cea1 --- /dev/null +++ b/change/@fluentui-make-styles-e8e5cc37-473f-49b4-9102-50c88f7b022e.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "fix(makeStyles): handle comma separated selectors", + "packageName": "@fluentui/make-styles", + "email": "olfedias@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/make-styles/src/runtime/compileCSS.test.ts b/packages/make-styles/src/runtime/compileCSS.test.ts index 0f12cf5d0f1ca..57605623fa430 100644 --- a/packages/make-styles/src/runtime/compileCSS.test.ts +++ b/packages/make-styles/src/runtime/compileCSS.test.ts @@ -1,4 +1,4 @@ -import { compileCSS, CompileCSSOptions } from './compileCSS'; +import { compileCSS, CompileCSSOptions, normalizePseudoSelector } from './compileCSS'; const defaultOptions: Pick< CompileCSSOptions, @@ -155,3 +155,23 @@ describe('compileCSS', () => { }); }); }); + +describe('normalizePseudoSelector', () => { + it('handles basic ', () => { + expect(normalizePseudoSelector(':hover')).toMatchInlineSnapshot(`"&:hover"`); + }); + + it('handles spacing', () => { + expect(normalizePseudoSelector(' :hover')).toMatchInlineSnapshot(`"& :hover"`); + }); + + it('handles multiple pseudos', () => { + expect(normalizePseudoSelector(':focus:hover')).toMatchInlineSnapshot(`"&:focus:hover"`); + }); + + it('handles comma separated pseudos', () => { + expect(normalizePseudoSelector('& :hover, & :focus')).toMatchInlineSnapshot(`"& :hover, & :focus"`); + expect(normalizePseudoSelector(':focus,:hover')).toMatchInlineSnapshot(`"&:focus,&:hover"`); + expect(normalizePseudoSelector(':focus, :hover')).toMatchInlineSnapshot(`"&:focus,& :hover"`); + }); +}); diff --git a/packages/make-styles/src/runtime/compileCSS.ts b/packages/make-styles/src/runtime/compileCSS.ts index 6ac511af7642a..4a3226a6df5c4 100644 --- a/packages/make-styles/src/runtime/compileCSS.ts +++ b/packages/make-styles/src/runtime/compileCSS.ts @@ -19,10 +19,32 @@ export interface CompileCSSOptions { rtlValue?: number | string; } +const PSEUDO_SELECTOR_REGEX = /,( *[^ &])/g; + function repeatSelector(selector: string, times: number) { return new Array(times + 2).join(selector); } +/** + * Normalizes pseudo selectors to always contain &, requires to work properly with comma-separated selectors. + * + * @example + * ":hover" => "&:hover" + * " :hover" => "& :hover" + * ":hover,:focus" => "&:hover,&:focus" + * " :hover, :focus" => "& :hover,& :focus" + */ +export function normalizePseudoSelector(pseudoSelector: string): string { + return ( + '&' + + normalizeNestedProperty( + // Regex there replaces a comma, spaces and an ampersand if it's present with comma and an ampersand. + // This allows to normalize input, see examples in JSDoc. + pseudoSelector.replace(PSEUDO_SELECTOR_REGEX, ',&$1'), + ) + ); +} + export function compileCSSRules(cssRules: string): string[] { const rules: string[] = []; @@ -86,10 +108,12 @@ export function compileCSS(options: CompileCSSOptions): [string /* ltr definitio cssRule = `${globalSelector} { ${ltrRule}; ${rtlRule} }`; } else { - cssRule = `${classNameSelector}${pseudo} ${cssDeclaration};`; + const normalizedPseudo = normalizePseudoSelector(pseudo); + + cssRule = `${classNameSelector}{${normalizedPseudo} ${cssDeclaration}};`; if (rtlProperty) { - cssRule = `${cssRule}; ${rtlClassNameSelector}${pseudo} ${rtlCSSDeclaration};`; + cssRule = `${cssRule}; ${rtlClassNameSelector}${normalizedPseudo} ${rtlCSSDeclaration};`; } }