|
1 | 1 | let KEYWORDS = new Set(['inset', 'inherit', 'initial', 'revert', 'unset'])
|
2 |
| -let COMMA = /\,(?![^(]*\))/g // Comma separator that is not located between brackets. E.g.: `cubiz-bezier(a, b, c)` these don't count. |
3 | 2 | let SPACE = /\ +(?![^(]*\))/g // Similar to the one above, but with spaces instead.
|
4 | 3 | let LENGTH = /^-?(\d+|\.\d+)(.*?)$/g
|
5 | 4 |
|
| 5 | +let SPECIALS = /[(),]/g |
| 6 | + |
| 7 | +/** |
| 8 | + * This splits a string on top-level commas. |
| 9 | + * |
| 10 | + * Regex doesn't support recursion (at least not the JS-flavored version). |
| 11 | + * So we have to use a tiny state machine to keep track of paren vs comma |
| 12 | + * placement. Before we'd only exclude commas from the inner-most nested |
| 13 | + * set of parens rather than any commas that were not contained in parens |
| 14 | + * at all which is the intended behavior here. |
| 15 | + * |
| 16 | + * Expected behavior: |
| 17 | + * var(--a, 0 0 1px rgb(0, 0, 0)), 0 0 1px rgb(0, 0, 0) |
| 18 | + * ─┬─ ┬ ┬ ┬ |
| 19 | + * x x x ╰──────── Split because top-level |
| 20 | + * ╰──────────────┴──┴───────────── Ignored b/c inside >= 1 levels of parens |
| 21 | + * |
| 22 | + * @param {string} input |
| 23 | + */ |
| 24 | +function* splitByTopLevelCommas(input) { |
| 25 | + SPECIALS.lastIndex = -1 |
| 26 | + |
| 27 | + let depth = 0 |
| 28 | + let lastIndex = 0 |
| 29 | + let found = false |
| 30 | + |
| 31 | + // Find all parens & commas |
| 32 | + // And only split on commas if they're top-level |
| 33 | + for (let match of input.matchAll(SPECIALS)) { |
| 34 | + if (match[0] === '(') depth++ |
| 35 | + if (match[0] === ')') depth-- |
| 36 | + if (match[0] === ',' && depth === 0) { |
| 37 | + found = true |
| 38 | + |
| 39 | + yield input.substring(lastIndex, match.index) |
| 40 | + lastIndex = match.index + match[0].length |
| 41 | + } |
| 42 | + } |
| 43 | + |
| 44 | + // Provide the last segment of the string if available |
| 45 | + // Otherwise the whole string since no commas were found |
| 46 | + // This mirrors the behavior of string.split() |
| 47 | + if (found) { |
| 48 | + yield input.substring(lastIndex) |
| 49 | + } else { |
| 50 | + yield input |
| 51 | + } |
| 52 | +} |
| 53 | + |
6 | 54 | export function parseBoxShadowValue(input) {
|
7 |
| - let shadows = input.split(COMMA) |
| 55 | + let shadows = Array.from(splitByTopLevelCommas(input)) |
8 | 56 | return shadows.map((shadow) => {
|
9 | 57 | let value = shadow.trim()
|
10 | 58 | let result = { raw: value }
|
|
0 commit comments