flyonui
Version:
The easiest, free and open-source Tailwind CSS component library with semantic classes.
153 lines (130 loc) • 4.42 kB
JavaScript
const defaultExcludedPrefixes = ['color-', 'size-', 'radius-', 'border', 'depth', 'noise']
const shouldExcludeVariable = (variableName, excludedPrefixes) => {
if (variableName.startsWith('tw')) {
return true
}
return excludedPrefixes.some(excludedPrefix => variableName.startsWith(excludedPrefix))
}
const prefixVariable = (variableName, prefix, excludedPrefixes) => {
if (shouldExcludeVariable(variableName, excludedPrefixes)) {
return variableName
}
return `${prefix}${variableName}`
}
const getPrefixedSelector = (selector, prefix) => {
if (!selector.startsWith('.')) return selector
return `.${prefix}${selector.slice(1)}`
}
const getPrefixedKey = (key, prefix, excludedPrefixes) => {
const prefixAmpDot = prefix ? `&.${prefix}` : ''
if (!prefix) return key
if (key.startsWith('--')) {
const variableName = key.slice(2)
return `--${prefixVariable(variableName, prefix, excludedPrefixes)}`
}
if (key.startsWith('@') || key.startsWith('[')) {
return key
}
if (key.startsWith('&')) {
// If it's a complex selector with :not(), :has(), etc.
if (key.match(/:[a-z-]+\(/)) {
return key.replace(/\.([\w-]+)/g, `.${prefix}$1`)
}
// For simple &. cases
if (key.startsWith('&.')) {
return `${prefixAmpDot}${key.slice(2)}`
}
// For other & cases (like &:hover or &:not(...))
return key.replace(/\.([\w-]+)/g, `.${prefix}$1`)
}
if (key.startsWith(':')) {
return key.replace(/\.([\w-]+)/g, `.${prefix}$1`)
}
if (key.includes('.') && !key.includes(' ') && !key.includes('>') && !key.includes('+') && !key.includes('~')) {
return key
.split('.')
.filter(Boolean)
.map(part => prefix + part)
.join('.')
.replace(/^/, '.')
}
if (key.includes('>') || key.includes('+') || key.includes('~')) {
// For comma-separated selectors
if (key.includes(',')) {
return key
.split(/\s*,\s*/)
.map(part => {
// Replace class names with prefixed versions for each part
return part.replace(/\.([\w-]+)/g, `.${prefix}$1`)
})
.join(', ')
}
// For simple combinators (not comma-separated)
let processedKey = key.replace(/\.([\w-]+)/g, `.${prefix}$1`)
// Add a space before combinators at the beginning
if (processedKey.startsWith('>') || processedKey.startsWith('+') || processedKey.startsWith('~')) {
processedKey = ` ${processedKey}`
}
return processedKey
}
if (key.includes(' ')) {
return key
.split(/\s+/)
.map(part => {
if (part.startsWith('.')) {
return getPrefixedSelector(part, prefix)
}
return part
})
.join(' ')
}
if (key.includes(':')) {
const [selector, ...pseudo] = key.split(':')
if (selector.startsWith('.')) {
return `${getPrefixedSelector(selector, prefix)}:${pseudo.join(':')}`
}
return key.replace(/\.([\w-]+)/g, `.${prefix}$1`)
}
if (key.startsWith('.')) {
return getPrefixedSelector(key, prefix)
}
return key
}
const processArrayValue = (value, prefix, excludedPrefixes) => {
return value.map(item => {
if (typeof item === 'string') {
if (item.startsWith('.')) {
return prefix ? `.${prefix}${item.slice(1)}` : item
}
return processStringValue(item, prefix, excludedPrefixes)
}
return item
})
}
const processStringValue = (value, prefix, excludedPrefixes) => {
if (prefix === 0) return value
return value.replace(/var\(--([^)]+)\)/g, (match, variableName) => {
if (shouldExcludeVariable(variableName, excludedPrefixes)) {
return match
}
return `var(--${prefix}${variableName})`
})
}
const processValue = (value, prefix, excludedPrefixes) => {
if (Array.isArray(value)) {
return processArrayValue(value, prefix, excludedPrefixes)
} else if (typeof value === 'object' && value !== null) {
return addPrefix(value, prefix, excludedPrefixes)
} else if (typeof value === 'string') {
return processStringValue(value, prefix, excludedPrefixes)
} else {
return value
}
}
export const addPrefix = (obj, prefix, excludedPrefixes = defaultExcludedPrefixes) => {
return Object.entries(obj).reduce((result, [key, value]) => {
const newKey = getPrefixedKey(key, prefix, excludedPrefixes)
result[newKey] = processValue(value, prefix, excludedPrefixes)
return result
}, {})
}