@acemir/cssom 0.9.2 → 0.9.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/CSSOM.js CHANGED
@@ -1807,16 +1807,19 @@ CSSOM.parse = function parse(token, errorHandler) {
1807
1807
  * - Class selectors (e.g., `.container`)
1808
1808
  * - Attribute selectors (e.g., `[type="text"]`)
1809
1809
  * - Pseudo-classes and pseudo-elements (e.g., `:hover`, `::before`, `:nth-child(2)`)
1810
+ * - Pseudo-classes with nested parentheses, including cases where parentheses are nested inside arguments,
1811
+ * such as `:has(.sel:nth-child(3n))`
1810
1812
  * - The parent selector (`&`)
1811
1813
  * - Combinators (`>`, `+`, `~`) with optional whitespace
1812
1814
  * - Whitespace (descendant combinator)
1813
1815
  *
1814
1816
  * The pattern ensures that a string consists only of valid basic selector components,
1815
- * possibly repeated and combined, but does not match full CSS selector groups separated by commas.
1817
+ * possibly repeated and combined, including pseudo-classes with nested parentheses,
1818
+ * but does not match full CSS selector groups separated by commas.
1816
1819
  *
1817
1820
  * @type {RegExp}
1818
1821
  */
1819
- var basicSelectorRegExp = /^([a-zA-Z][a-zA-Z0-9_-]*|\*|#[a-zA-Z0-9_-]+|\.[a-zA-Z0-9_-]+|\[[^\[\]]*\]|::?[a-zA-Z0-9_-]+(?:\([^\(\)]*\))?|&|\s*[>+~]\s*|\s+)+$/;
1822
+ var basicSelectorRegExp = /^([a-zA-Z][a-zA-Z0-9_-]*|\*|#[a-zA-Z0-9_-]+|\.[a-zA-Z0-9_-]+|\[[^\[\]]*(?:\s+[iI])?\]|::?[a-zA-Z0-9_-]+(?:\(((?:[^()"]+|"[^"]*"|'[^']*'|\((?:[^()"]+|"[^"]*"|'[^']*')*\))*?)\))?|&|\s*[>+~]\s*|\s+)+$/;
1820
1823
 
1821
1824
  /**
1822
1825
  * Regular expression to match CSS pseudo-classes with arguments.
@@ -1825,16 +1828,17 @@ CSSOM.parse = function parse(token, errorHandler) {
1825
1828
  *
1826
1829
  * Capture groups:
1827
1830
  * 1. The pseudo-class name (letters and hyphens).
1828
- * 2. The argument inside the parentheses (any characters except a closing parenthesis).
1831
+ * 2. The argument inside the parentheses (can contain nested parentheses, quoted strings, and other characters.).
1829
1832
  *
1830
1833
  * Global flag (`g`) is used to find all matches in the input string.
1831
1834
  *
1832
- * Example match: `:nth-child(2n+1)`
1833
- * - Group 1: "nth-child"
1834
- * - Group 2: "2n+1"
1835
+ * Example matches:
1836
+ * - :nth-child(2n+1)
1837
+ * - :has(.sel:nth-child(3n))
1838
+ * - :not(".foo, .bar")
1835
1839
  * @type {RegExp}
1836
1840
  */
1837
- var globalPseudoClassRegExp = /:([a-zA-Z-]+)\(([^)]*)\)/g;
1841
+ var globalPseudoClassRegExp = /:([a-zA-Z-]+)\(((?:[^()"]+|"[^"]*"|'[^']*'|\((?:[^()"]+|"[^"]*"|'[^']*')*\))*?)\)/g;
1838
1842
 
1839
1843
  /**
1840
1844
  * Parses a CSS selector string and splits it into parts, handling nested parentheses.
@@ -1846,30 +1850,42 @@ CSSOM.parse = function parse(token, errorHandler) {
1846
1850
  * @param {string} selector - The CSS selector string to parse.
1847
1851
  * @returns {string[]} An array of selector parts, split by top-level commas, with whitespace trimmed.
1848
1852
  */
1849
- function parseNestedSelectors(selector) {
1853
+ function parseAndSplitNestedSelectors(selector) {
1850
1854
  var depth = 0;
1851
1855
  var buffer = "";
1852
1856
  var parts = [];
1857
+ var inSingleQuote = false;
1858
+ var inDoubleQuote = false;
1853
1859
  var i, char;
1854
1860
 
1855
1861
  for (i = 0; i < selector.length; i++) {
1856
1862
  char = selector.charAt(i);
1857
1863
 
1858
- if (char === '(') {
1859
- depth++;
1864
+ if (char === "'" && !inDoubleQuote) {
1865
+ inSingleQuote = !inSingleQuote;
1860
1866
  buffer += char;
1861
- } else if (char === ')') {
1862
- depth--;
1867
+ } else if (char === '"' && !inSingleQuote) {
1868
+ inDoubleQuote = !inDoubleQuote;
1863
1869
  buffer += char;
1864
- if (depth === 0) {
1865
- parts.push(buffer.replace(/^\s+|\s+$/g, ""));
1870
+ } else if (!inSingleQuote && !inDoubleQuote) {
1871
+ if (char === '(') {
1872
+ depth++;
1873
+ buffer += char;
1874
+ } else if (char === ')') {
1875
+ depth--;
1876
+ buffer += char;
1877
+ if (depth === 0) {
1878
+ parts.push(buffer.replace(/^\s+|\s+$/g, ""));
1879
+ buffer = "";
1880
+ }
1881
+ } else if (char === ',' && depth === 0) {
1882
+ if (buffer.replace(/^\s+|\s+$/g, "")) {
1883
+ parts.push(buffer.replace(/^\s+|\s+$/g, ""));
1884
+ }
1866
1885
  buffer = "";
1886
+ } else {
1887
+ buffer += char;
1867
1888
  }
1868
- } else if (char === ',' && depth === 0) {
1869
- if (buffer.replace(/^\s+|\s+$/g, "")) {
1870
- parts.push(buffer.replace(/^\s+|\s+$/g, ""));
1871
- }
1872
- buffer = "";
1873
1889
  } else {
1874
1890
  buffer += char;
1875
1891
  }
@@ -1908,7 +1924,7 @@ CSSOM.parse = function parse(token, errorHandler) {
1908
1924
  while ((match = pseudoClassRegExp.exec(selector)) !== null) {
1909
1925
  var pseudoClass = match[1];
1910
1926
  if (selectorListPseudoClasses.hasOwnProperty(pseudoClass)) {
1911
- nestedSelectors = parseNestedSelectors(match[2]);
1927
+ nestedSelectors = parseAndSplitNestedSelectors(match[2]);
1912
1928
  // Validate each nested selector
1913
1929
  for (i = 0; i < nestedSelectors.length; i++) {
1914
1930
  if (!validateSelector(nestedSelectors[i])) {
@@ -1931,9 +1947,10 @@ CSSOM.parse = function parse(token, errorHandler) {
1931
1947
  */
1932
1948
  function isValidSelectorText(selectorText) {
1933
1949
  // Split selectorText by commas and validate each part
1934
- var selectors = selectorText.split(',');
1950
+ var selectors = parseAndSplitNestedSelectors(selectorText);
1935
1951
  for (var i = 0; i < selectors.length; i++) {
1936
- if (!validateSelector(selectors[i].replace(/^\s+|\s+$/g, ""))) {
1952
+ var processedSelectors = selectors[i].replace(/^\s+|\s+$/g, "");
1953
+ if (!validateSelector(processedSelectors)) {
1937
1954
  return false;
1938
1955
  }
1939
1956
  }
@@ -2159,7 +2176,6 @@ CSSOM.parse = function parse(token, errorHandler) {
2159
2176
  }
2160
2177
 
2161
2178
  currentScope = parentRule = styleRule;
2162
- console.log('sel out', buffer);
2163
2179
  styleRule.selectorText = buffer.trim();
2164
2180
  styleRule.style.__starts = i;
2165
2181
  styleRule.parentStyleSheet = styleSheet;
@@ -2289,9 +2305,8 @@ CSSOM.parse = function parse(token, errorHandler) {
2289
2305
  }
2290
2306
 
2291
2307
  styleRule = new CSSOM.CSSStyleRule();
2292
- console.log('sel in', buffer);
2293
2308
  // In a nested selector, ensure each selector contains '&' at the beginning, except for selectors that already have '&' somewhere
2294
- styleRule.selectorText = parseNestedSelectors(buffer.trim()).map(function(sel) {
2309
+ styleRule.selectorText = parseAndSplitNestedSelectors(buffer.trim()).map(function(sel) {
2295
2310
  return sel.indexOf('&') === -1 ? '& ' + sel : sel;
2296
2311
  }).join(', ');
2297
2312
  styleRule.style.__starts = i - buffer.length;
package/lib/parse.js CHANGED
@@ -240,16 +240,19 @@ CSSOM.parse = function parse(token, errorHandler) {
240
240
  * - Class selectors (e.g., `.container`)
241
241
  * - Attribute selectors (e.g., `[type="text"]`)
242
242
  * - Pseudo-classes and pseudo-elements (e.g., `:hover`, `::before`, `:nth-child(2)`)
243
+ * - Pseudo-classes with nested parentheses, including cases where parentheses are nested inside arguments,
244
+ * such as `:has(.sel:nth-child(3n))`
243
245
  * - The parent selector (`&`)
244
246
  * - Combinators (`>`, `+`, `~`) with optional whitespace
245
247
  * - Whitespace (descendant combinator)
246
248
  *
247
249
  * The pattern ensures that a string consists only of valid basic selector components,
248
- * possibly repeated and combined, but does not match full CSS selector groups separated by commas.
250
+ * possibly repeated and combined, including pseudo-classes with nested parentheses,
251
+ * but does not match full CSS selector groups separated by commas.
249
252
  *
250
253
  * @type {RegExp}
251
254
  */
252
- var basicSelectorRegExp = /^([a-zA-Z][a-zA-Z0-9_-]*|\*|#[a-zA-Z0-9_-]+|\.[a-zA-Z0-9_-]+|\[[^\[\]]*\]|::?[a-zA-Z0-9_-]+(?:\([^\(\)]*\))?|&|\s*[>+~]\s*|\s+)+$/;
255
+ var basicSelectorRegExp = /^([a-zA-Z][a-zA-Z0-9_-]*|\*|#[a-zA-Z0-9_-]+|\.[a-zA-Z0-9_-]+|\[[^\[\]]*(?:\s+[iI])?\]|::?[a-zA-Z0-9_-]+(?:\(((?:[^()"]+|"[^"]*"|'[^']*'|\((?:[^()"]+|"[^"]*"|'[^']*')*\))*?)\))?|&|\s*[>+~]\s*|\s+)+$/;
253
256
 
254
257
  /**
255
258
  * Regular expression to match CSS pseudo-classes with arguments.
@@ -258,16 +261,17 @@ CSSOM.parse = function parse(token, errorHandler) {
258
261
  *
259
262
  * Capture groups:
260
263
  * 1. The pseudo-class name (letters and hyphens).
261
- * 2. The argument inside the parentheses (any characters except a closing parenthesis).
264
+ * 2. The argument inside the parentheses (can contain nested parentheses, quoted strings, and other characters.).
262
265
  *
263
266
  * Global flag (`g`) is used to find all matches in the input string.
264
267
  *
265
- * Example match: `:nth-child(2n+1)`
266
- * - Group 1: "nth-child"
267
- * - Group 2: "2n+1"
268
+ * Example matches:
269
+ * - :nth-child(2n+1)
270
+ * - :has(.sel:nth-child(3n))
271
+ * - :not(".foo, .bar")
268
272
  * @type {RegExp}
269
273
  */
270
- var globalPseudoClassRegExp = /:([a-zA-Z-]+)\(([^)]*)\)/g;
274
+ var globalPseudoClassRegExp = /:([a-zA-Z-]+)\(((?:[^()"]+|"[^"]*"|'[^']*'|\((?:[^()"]+|"[^"]*"|'[^']*')*\))*?)\)/g;
271
275
 
272
276
  /**
273
277
  * Parses a CSS selector string and splits it into parts, handling nested parentheses.
@@ -279,30 +283,42 @@ CSSOM.parse = function parse(token, errorHandler) {
279
283
  * @param {string} selector - The CSS selector string to parse.
280
284
  * @returns {string[]} An array of selector parts, split by top-level commas, with whitespace trimmed.
281
285
  */
282
- function parseNestedSelectors(selector) {
286
+ function parseAndSplitNestedSelectors(selector) {
283
287
  var depth = 0;
284
288
  var buffer = "";
285
289
  var parts = [];
290
+ var inSingleQuote = false;
291
+ var inDoubleQuote = false;
286
292
  var i, char;
287
293
 
288
294
  for (i = 0; i < selector.length; i++) {
289
295
  char = selector.charAt(i);
290
296
 
291
- if (char === '(') {
292
- depth++;
297
+ if (char === "'" && !inDoubleQuote) {
298
+ inSingleQuote = !inSingleQuote;
293
299
  buffer += char;
294
- } else if (char === ')') {
295
- depth--;
300
+ } else if (char === '"' && !inSingleQuote) {
301
+ inDoubleQuote = !inDoubleQuote;
296
302
  buffer += char;
297
- if (depth === 0) {
298
- parts.push(buffer.replace(/^\s+|\s+$/g, ""));
303
+ } else if (!inSingleQuote && !inDoubleQuote) {
304
+ if (char === '(') {
305
+ depth++;
306
+ buffer += char;
307
+ } else if (char === ')') {
308
+ depth--;
309
+ buffer += char;
310
+ if (depth === 0) {
311
+ parts.push(buffer.replace(/^\s+|\s+$/g, ""));
312
+ buffer = "";
313
+ }
314
+ } else if (char === ',' && depth === 0) {
315
+ if (buffer.replace(/^\s+|\s+$/g, "")) {
316
+ parts.push(buffer.replace(/^\s+|\s+$/g, ""));
317
+ }
299
318
  buffer = "";
319
+ } else {
320
+ buffer += char;
300
321
  }
301
- } else if (char === ',' && depth === 0) {
302
- if (buffer.replace(/^\s+|\s+$/g, "")) {
303
- parts.push(buffer.replace(/^\s+|\s+$/g, ""));
304
- }
305
- buffer = "";
306
322
  } else {
307
323
  buffer += char;
308
324
  }
@@ -341,7 +357,7 @@ CSSOM.parse = function parse(token, errorHandler) {
341
357
  while ((match = pseudoClassRegExp.exec(selector)) !== null) {
342
358
  var pseudoClass = match[1];
343
359
  if (selectorListPseudoClasses.hasOwnProperty(pseudoClass)) {
344
- nestedSelectors = parseNestedSelectors(match[2]);
360
+ nestedSelectors = parseAndSplitNestedSelectors(match[2]);
345
361
  // Validate each nested selector
346
362
  for (i = 0; i < nestedSelectors.length; i++) {
347
363
  if (!validateSelector(nestedSelectors[i])) {
@@ -364,9 +380,10 @@ CSSOM.parse = function parse(token, errorHandler) {
364
380
  */
365
381
  function isValidSelectorText(selectorText) {
366
382
  // Split selectorText by commas and validate each part
367
- var selectors = selectorText.split(',');
383
+ var selectors = parseAndSplitNestedSelectors(selectorText);
368
384
  for (var i = 0; i < selectors.length; i++) {
369
- if (!validateSelector(selectors[i].replace(/^\s+|\s+$/g, ""))) {
385
+ var processedSelectors = selectors[i].replace(/^\s+|\s+$/g, "");
386
+ if (!validateSelector(processedSelectors)) {
370
387
  return false;
371
388
  }
372
389
  }
@@ -592,7 +609,6 @@ CSSOM.parse = function parse(token, errorHandler) {
592
609
  }
593
610
 
594
611
  currentScope = parentRule = styleRule;
595
- console.log('sel out', buffer);
596
612
  styleRule.selectorText = buffer.trim();
597
613
  styleRule.style.__starts = i;
598
614
  styleRule.parentStyleSheet = styleSheet;
@@ -722,9 +738,8 @@ CSSOM.parse = function parse(token, errorHandler) {
722
738
  }
723
739
 
724
740
  styleRule = new CSSOM.CSSStyleRule();
725
- console.log('sel in', buffer);
726
741
  // In a nested selector, ensure each selector contains '&' at the beginning, except for selectors that already have '&' somewhere
727
- styleRule.selectorText = parseNestedSelectors(buffer.trim()).map(function(sel) {
742
+ styleRule.selectorText = parseAndSplitNestedSelectors(buffer.trim()).map(function(sel) {
728
743
  return sel.indexOf('&') === -1 ? '& ' + sel : sel;
729
744
  }).join(', ');
730
745
  styleRule.style.__starts = i - buffer.length;
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "parser",
8
8
  "styleSheet"
9
9
  ],
10
- "version": "0.9.2",
10
+ "version": "0.9.4",
11
11
  "author": "Nikita Vasilyev <me@elv1s.ru>",
12
12
  "contributors": [
13
13
  "Acemir Sousa Mendes <acemirsm@gmail.com>"