-
Notifications
You must be signed in to change notification settings - Fork 219
Perform CSS variable resolution recursively #1168
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4bea5c7
ddd48b7
ccb7bdf
95271a5
3725eae
733c03a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,12 +16,31 @@ export interface Range { | |
end: number | ||
} | ||
|
||
export interface ReplacerOptions { | ||
/** | ||
* Whether or not the replacement should be performed recursively | ||
* | ||
* default: true | ||
*/ | ||
recursive?: boolean | ||
|
||
/** | ||
* How to replace the CSS variable | ||
*/ | ||
replace: CssVarReplacer | ||
} | ||
|
||
export type CssVarReplacer = (node: CssVariable) => string | null | ||
|
||
/** | ||
* Replace all var expressions in a string using the replacer function | ||
*/ | ||
export function replaceCssVars(str: string, replace: CssVarReplacer): string { | ||
export function replaceCssVars( | ||
str: string, | ||
{ replace, recursive = true }: ReplacerOptions, | ||
): string { | ||
let seen = new Set<string>() | ||
|
||
for (let i = 0; i < str.length; ++i) { | ||
if (!str.startsWith('var(', i)) continue | ||
|
||
|
@@ -33,6 +52,8 @@ export function replaceCssVars(str: string, replace: CssVarReplacer): string { | |
depth++ | ||
} else if (str[j] === ')' && depth > 0) { | ||
depth-- | ||
} else if (str[j] === '\\') { | ||
j++ | ||
} else if (str[j] === ',' && depth === 0 && fallbackStart === null) { | ||
fallbackStart = j + 1 | ||
} else if (str[j] === ')' && depth === 0) { | ||
|
@@ -58,9 +79,20 @@ export function replaceCssVars(str: string, replace: CssVarReplacer): string { | |
str = str.slice(0, i) + replacement + str.slice(j + 1) | ||
} | ||
|
||
// We don't want to skip past anything here because `replacement` | ||
// might contain more var(…) calls in which case `i` will already | ||
// be pointing at the right spot to start looking for them | ||
// Move the index back one so it can look at the spot again since it'll | ||
// be incremented by the outer loop. However, since we're replacing | ||
// variables recursively we might end up in a loop so we need to keep | ||
// track of which variables we've already seen and where they were | ||
// replaced to avoid infinite loops. | ||
if (recursive) { | ||
let key = `${i}:${replacement}` | ||
|
||
if (!seen.has(key)) { | ||
seen.add(key) | ||
i -= 1 | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @RobinMalfait or @philipp-spiess can y'all think of a way to break this? It has the potential for infinite recursion (due to circular theme key lookups) but I guarded against it here. I think this should be sufficient but if you could give it some thought that would be much appreciated. |
||
|
||
break | ||
} | ||
} | ||
|
Uh oh!
There was an error while loading. Please reload this page.