aliaset
Version:
twind monorepo
73 lines (60 loc) • 2.48 kB
text/typescript
import type { TwindRule } from '../types'
import { escape } from '../utils'
export function stringify(rule: TwindRule): string | undefined {
if (rule.d) {
const groups: string[] = []
const selector = replaceEach(
// merge all conditions into a selector string
rule.r.reduce((selector, condition) => {
if (condition[0] == '@') {
groups.push(condition)
return selector
}
// Go over the selector and replace the matching multiple selectors if any
return condition ? merge(selector, condition) : selector
}, '&'),
// replace '&' with rule name or an empty string
(selectorPart) => replaceReference(selectorPart, rule.n ? '.' + escape(rule.n) : ''),
)
if (selector) {
groups.push(selector.replace(/:merge\((.+?)\)/g, '$1'))
}
return groups.reduceRight((body, grouping) => grouping + '{' + body + '}', rule.d)
}
}
function replaceEach(selector: string, iteratee: (selectorPart: string) => string): string {
return selector.replace(
/ *((?:\(.+?\)|\[.+?\]|[^,])+) *(,|$)/g,
(_, selectorPart: string, comma: string) => iteratee(selectorPart) + comma,
)
}
function replaceReference(selector: string, reference: string): string {
return selector.replace(/&/g, reference)
}
function merge(selector: string, condition: string): string {
return replaceEach(selector, (selectorPart) =>
replaceEach(
condition,
// If the current condition has a nested selector replace it
(conditionPart) => {
const mergeMatch = /(:merge\(.+?\))(:[a-z-]+|\\[.+])/.exec(conditionPart)
if (mergeMatch) {
const selectorIndex = selectorPart.indexOf(mergeMatch[1])
if (~selectorIndex) {
// [':merge(.group):hover .rule', ':merge(.group):focus &'] -> ':merge(.group):focus:hover .rule'
// ':merge(.group)' + ':focus' + ':hover .rule'
return (
selectorPart.slice(0, selectorIndex) +
mergeMatch[0] +
selectorPart.slice(selectorIndex + mergeMatch[1].length)
)
}
// [':merge(.peer):focus~&', ':merge(.group):hover &'] -> ':merge(.peer):focus~:merge(.group):hover &'
return replaceReference(selectorPart, conditionPart)
}
// Return the current selector with the key matching multiple selectors if any
return replaceReference(conditionPart, selectorPart)
},
),
)
}