From e95a15ebeacb187352d18bbe38b17829ab10bdf8 Mon Sep 17 00:00:00 2001 From: tomekcz Date: Tue, 6 Jan 2026 13:50:09 +0100 Subject: [PATCH 1/3] Partially allow complex state in parseSelector --- .../core/src/parser/model/BrowserParserCss.ts | 33 +++++++++++-------- .../core/test/specs/parser/model/ParserCss.ts | 6 ++++ 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/packages/core/src/parser/model/BrowserParserCss.ts b/packages/core/src/parser/model/BrowserParserCss.ts index a7c16dcc1..4be871b70 100644 --- a/packages/core/src/parser/model/BrowserParserCss.ts +++ b/packages/core/src/parser/model/BrowserParserCss.ts @@ -61,30 +61,35 @@ const SINGLE_AT_RULE_NAMES = SINGLE_AT_RULE_TYPES.map((n) => AT_RULE_NAMES[n]); * console.log(res); * // { result: [['test1'], ['test1', 'test2']], add: ['.test2 .test3'] } */ -export const parseSelector = (str = '') => { +const parseSelector = (str = '') => { const add: string[] = []; const result: string[][] = []; const sels = str.split(','); - for (var i = 0, len = sels.length; i < len; i++) { - var sel = sels[i].trim(); + // Will accept only concatenated classes and last + // class might be with state (eg. :hover) complex state (:hover:not(.active)) + // as long as the state does not contain commas. + // Can also accept SINGLE ID selectors, eg. `#myid`, `#myid:hover` + // Composed are not valid: `#myid.some-class`, `#myid.some-class:hover - // Will accept only concatenated classes and last - // class might be with state (eg. :hover), nothing else. - // Can also accept SINGLE ID selectors, eg. `#myid`, `#myid:hover` - // Composed are not valid: `#myid.some-class`, `#myid.some-class:hover` - if (/^(\.{1}[\w\-]+)+(:{1,2}[\w\-()]+)?$/gi.test(sel) || /^(#{1}[\w\-]+){1}(:{1,2}[\w\-()]+)?$/gi.test(sel)) { - var cls = sel.split('.').filter(Boolean); - result.push(cls); + const checkForClass = + /^(\.[\w\-]+)+((:{1,2}[\w\-]+)(\([^)]*\))?)*$/; + + const checkForId = + /^#[\w\-]+((:{1,2}[\w\-]+)(\([^)]*\))?)*$/; + + for (let i = 0; i < sels.length; i++) { + const sel = sels[i].trim(); + + if (checkForClass.test(sel) || checkForId.test(sel)) { + const parts = sel.split(/\.(?![^()]*\))/).filter(Boolean); + result.push(parts); } else { add.push(sel); } } - return { - result, - add, - }; + return { result, add }; }; /** diff --git a/packages/core/test/specs/parser/model/ParserCss.ts b/packages/core/test/specs/parser/model/ParserCss.ts index c72919b13..576e6e8df 100644 --- a/packages/core/test/specs/parser/model/ParserCss.ts +++ b/packages/core/test/specs/parser/model/ParserCss.ts @@ -63,6 +63,12 @@ describe('ParserCss', () => { var result = [['test4', 'test5:hover']]; expect(parseSelector(str).result).toEqual(result); }); + + test('Parse selectors with complex state', () => { + var str = '.test1. test2, .test2>test3, .test4.test5:hover:not(.active.some-class)'; + var result = [['test4', 'test5:hover:not(.active.some-class)']]; + expect(parseSelector(str).result).toEqual(result); + }); }); test('Parse simple rule', () => { From 60ca8a8ee0346e4a851cbdabc64e7007c496c84b Mon Sep 17 00:00:00 2001 From: tomekcz Date: Tue, 6 Jan 2026 13:52:59 +0100 Subject: [PATCH 2/3] removed export by mistake --- packages/core/src/parser/model/BrowserParserCss.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/parser/model/BrowserParserCss.ts b/packages/core/src/parser/model/BrowserParserCss.ts index 4be871b70..d7c0569ff 100644 --- a/packages/core/src/parser/model/BrowserParserCss.ts +++ b/packages/core/src/parser/model/BrowserParserCss.ts @@ -61,7 +61,7 @@ const SINGLE_AT_RULE_NAMES = SINGLE_AT_RULE_TYPES.map((n) => AT_RULE_NAMES[n]); * console.log(res); * // { result: [['test1'], ['test1', 'test2']], add: ['.test2 .test3'] } */ -const parseSelector = (str = '') => { +export const parseSelector = (str = '') => { const add: string[] = []; const result: string[][] = []; const sels = str.split(','); From c4742bf8133788aabae021400b2831127fbd0fef Mon Sep 17 00:00:00 2001 From: tomekcz Date: Fri, 9 Jan 2026 02:02:18 +0100 Subject: [PATCH 3/3] prettier fix --- packages/core/src/parser/model/BrowserParserCss.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/core/src/parser/model/BrowserParserCss.ts b/packages/core/src/parser/model/BrowserParserCss.ts index d7c0569ff..459acee2e 100644 --- a/packages/core/src/parser/model/BrowserParserCss.ts +++ b/packages/core/src/parser/model/BrowserParserCss.ts @@ -72,11 +72,9 @@ export const parseSelector = (str = '') => { // Can also accept SINGLE ID selectors, eg. `#myid`, `#myid:hover` // Composed are not valid: `#myid.some-class`, `#myid.some-class:hover - const checkForClass = - /^(\.[\w\-]+)+((:{1,2}[\w\-]+)(\([^)]*\))?)*$/; + const checkForClass = /^(\.[\w\-]+)+((:{1,2}[\w\-]+)(\([^)]*\))?)*$/; - const checkForId = - /^#[\w\-]+((:{1,2}[\w\-]+)(\([^)]*\))?)*$/; + const checkForId = /^#[\w\-]+((:{1,2}[\w\-]+)(\([^)]*\))?)*$/; for (let i = 0; i < sels.length; i++) { const sel = sels[i].trim();