Support opacity modifiers for colors in JIT (#4348)
* Support opacity modifiers for colors in JIT * Add test for function colors * Support opacity modifiers for plugins with arbitrary "any" type
This commit is contained in:
parent
6be797633f
commit
87df93de0a
7 changed files with 260 additions and 20 deletions
|
|
@ -27,9 +27,9 @@ function* candidatePermutations(candidate, lastIndex = Infinity) {
|
|||
if (lastIndex === Infinity && candidate.endsWith(']')) {
|
||||
let bracketIdx = candidate.lastIndexOf('[')
|
||||
|
||||
// If character before `[` isn't a dash, this isn't a dynamic class
|
||||
// If character before `[` isn't a dash or a slash, this isn't a dynamic class
|
||||
// eg. string[]
|
||||
dashIdx = candidate[bracketIdx - 1] === '-' ? bracketIdx - 1 : -1
|
||||
dashIdx = ['-', '/'].includes(candidate[bracketIdx - 1]) ? bracketIdx - 1 : -1
|
||||
} else {
|
||||
dashIdx = candidate.lastIndexOf('-', lastIndex)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -541,9 +541,10 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
|
|||
|
||||
function wrapped(modifier) {
|
||||
let { type = 'any' } = options
|
||||
let [value, coercedType] = coerceValue(type, modifier, options.values)
|
||||
type = [].concat(type)
|
||||
let [value, coercedType] = coerceValue(type, modifier, options.values, tailwindConfig)
|
||||
|
||||
if (type !== coercedType || value === undefined) {
|
||||
if (!type.includes(coercedType) || value === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export default function () {
|
|||
{
|
||||
values: flattenColorPalette(theme('fill')),
|
||||
variants: variants('fill'),
|
||||
type: 'any',
|
||||
type: ['color', 'any'],
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export default function () {
|
|||
let options = {
|
||||
values: flattenColorPalette(theme('gradientColorStops')),
|
||||
variants: variants('gradientColorStops'),
|
||||
type: 'any',
|
||||
type: ['color', 'any'],
|
||||
}
|
||||
|
||||
matchUtilities(
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export default function () {
|
|||
{
|
||||
values: flattenColorPalette(theme('placeholderColor')),
|
||||
variants: variants('placeholderColor'),
|
||||
type: 'any',
|
||||
type: ['color', 'any'],
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import selectorParser from 'postcss-selector-parser'
|
|||
import postcss from 'postcss'
|
||||
import createColor from 'color'
|
||||
import escapeCommas from './escapeCommas'
|
||||
import { withAlphaValue } from './withAlphaVariable'
|
||||
|
||||
export function updateAllClasses(selectors, updateClass) {
|
||||
let parser = selectorParser((selectors) => {
|
||||
|
|
@ -148,16 +149,50 @@ export function asList(modifier, lookup = {}) {
|
|||
})
|
||||
}
|
||||
|
||||
export function asColor(modifier, lookup = {}) {
|
||||
function isArbitraryValue(input) {
|
||||
return input.startsWith('[') && input.endsWith(']')
|
||||
}
|
||||
|
||||
function splitAlpha(modifier) {
|
||||
let slashIdx = modifier.lastIndexOf('/')
|
||||
|
||||
if (slashIdx === -1 || slashIdx === modifier.length - 1) {
|
||||
return [modifier]
|
||||
}
|
||||
|
||||
return [modifier.slice(0, slashIdx), modifier.slice(slashIdx + 1)]
|
||||
}
|
||||
|
||||
function isColor(value) {
|
||||
try {
|
||||
createColor(value)
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function asColor(modifier, lookup = {}, tailwindConfig = {}) {
|
||||
if (lookup[modifier] !== undefined) {
|
||||
return lookup[modifier]
|
||||
}
|
||||
|
||||
let [color, alpha] = splitAlpha(modifier)
|
||||
|
||||
if (lookup[color] !== undefined) {
|
||||
if (isArbitraryValue(alpha)) {
|
||||
return withAlphaValue(lookup[color], alpha.slice(1, -1))
|
||||
}
|
||||
|
||||
if (tailwindConfig.theme?.opacity?.[alpha] === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return withAlphaValue(lookup[color], tailwindConfig.theme.opacity[alpha])
|
||||
}
|
||||
|
||||
return asValue(modifier, lookup, {
|
||||
validate: (value) => {
|
||||
try {
|
||||
createColor(value)
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
},
|
||||
validate: isColor,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -208,14 +243,18 @@ function splitAtFirst(input, delim) {
|
|||
return (([first, ...rest]) => [first, rest.join(delim)])(input.split(delim))
|
||||
}
|
||||
|
||||
export function coerceValue(type, modifier, values) {
|
||||
if (modifier.startsWith('[') && modifier.endsWith(']')) {
|
||||
export function coerceValue(type, modifier, values, tailwindConfig) {
|
||||
let [scaleType, arbitraryType = scaleType] = [].concat(type)
|
||||
|
||||
if (isArbitraryValue(modifier)) {
|
||||
let [explicitType, value] = splitAtFirst(modifier.slice(1, -1), ':')
|
||||
|
||||
if (value.length > 0 && Object.keys(typeMap).includes(explicitType)) {
|
||||
return [asValue(`[${value}]`, values), explicitType]
|
||||
return [asValue(`[${value}]`, values, tailwindConfig), explicitType]
|
||||
}
|
||||
|
||||
return [typeMap[arbitraryType](modifier, values, tailwindConfig), arbitraryType]
|
||||
}
|
||||
|
||||
return [typeMap[type](modifier, values), type]
|
||||
return [typeMap[scaleType](modifier, values, tailwindConfig), scaleType]
|
||||
}
|
||||
|
|
|
|||
200
tests/jit/color-opacity-modifiers.test.js
Normal file
200
tests/jit/color-opacity-modifiers.test.js
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
import postcss from 'postcss'
|
||||
import path from 'path'
|
||||
import tailwind from '../../src/jit/index.js'
|
||||
|
||||
function run(input, config = {}) {
|
||||
const { currentTestName } = expect.getState()
|
||||
|
||||
return postcss(tailwind(config)).process(input, {
|
||||
from: `${path.resolve(__filename)}?test=${currentTestName}`,
|
||||
})
|
||||
}
|
||||
|
||||
test('basic color opacity modifier', async () => {
|
||||
let config = {
|
||||
mode: 'jit',
|
||||
purge: [
|
||||
{
|
||||
raw: '<div class="bg-red-500/50"></div>',
|
||||
},
|
||||
],
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `@tailwind utilities`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(`
|
||||
.bg-red-500\\/50 {
|
||||
background-color: rgba(239, 68, 68, 0.5);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('colors with slashes are matched first', async () => {
|
||||
let config = {
|
||||
mode: 'jit',
|
||||
purge: [
|
||||
{
|
||||
raw: '<div class="bg-red-500/50"></div>',
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
'red-500/50': '#ff0000',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `@tailwind utilities`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(`
|
||||
.bg-red-500\\/50 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(255, 0, 0, var(--tw-bg-opacity));
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('arbitrary color opacity modifier', async () => {
|
||||
let config = {
|
||||
mode: 'jit',
|
||||
purge: [
|
||||
{
|
||||
raw: 'bg-red-500/[var(--opacity)]',
|
||||
},
|
||||
],
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `@tailwind utilities`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(`
|
||||
.bg-red-500\\/\\[var\\(--opacity\\)\\] {
|
||||
background-color: rgba(239, 68, 68, var(--opacity));
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('missing alpha generates nothing', async () => {
|
||||
let config = {
|
||||
mode: 'jit',
|
||||
purge: [
|
||||
{
|
||||
raw: '<div class="bg-red-500/"></div>',
|
||||
},
|
||||
],
|
||||
theme: {},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `@tailwind utilities`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(``)
|
||||
})
|
||||
})
|
||||
|
||||
test('values not in the opacity config are ignored', async () => {
|
||||
let config = {
|
||||
mode: 'jit',
|
||||
purge: [
|
||||
{
|
||||
raw: '<div class="bg-red-500/29"></div>',
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
opacity: {
|
||||
0: '0',
|
||||
25: '0.25',
|
||||
5: '0.5',
|
||||
75: '0.75',
|
||||
100: '1',
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `@tailwind utilities`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(``)
|
||||
})
|
||||
})
|
||||
|
||||
test('function colors are supported', async () => {
|
||||
let config = {
|
||||
mode: 'jit',
|
||||
purge: [
|
||||
{
|
||||
raw: '<div class="bg-blue/50"></div>',
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
colors: {
|
||||
blue: ({ opacityValue }) => {
|
||||
return `rgba(var(--colors-blue), ${opacityValue})`
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `@tailwind utilities`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(`
|
||||
.bg-blue\\/50 {
|
||||
background-color: rgba(var(--colors-blue), 0.5);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
test('utilities that support any type are supported', async () => {
|
||||
let config = {
|
||||
mode: 'jit',
|
||||
purge: [
|
||||
{
|
||||
raw: `
|
||||
<div class="from-red-500/50"></div>
|
||||
<div class="fill-red-500/25"></div>
|
||||
<div class="placeholder-red-500/75"></div>
|
||||
`,
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
fill: (theme) => theme('colors'),
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
let css = `@tailwind utilities`
|
||||
|
||||
return run(css, config).then((result) => {
|
||||
expect(result.css).toMatchFormattedCss(`
|
||||
.from-red-500\\/50 {
|
||||
--tw-gradient-from: rgba(239, 68, 68, 0.5);
|
||||
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgba(239, 68, 68, 0));
|
||||
}
|
||||
.fill-red-500\\/25 {
|
||||
fill: rgba(239, 68, 68, 0.25);
|
||||
}
|
||||
.placeholder-red-500\\/75::placeholder {
|
||||
color: rgba(239, 68, 68, 0.75);
|
||||
}
|
||||
`)
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue