Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
Jordan Pittman
35bf48dffc Add mask image utilities 2024-06-04 15:57:01 -04:00
2 changed files with 365 additions and 0 deletions

View file

@ -8100,6 +8100,186 @@ test('bg', () => {
`)
})
test('mask', () => {
expect(
compileCss(
css`
@theme {
--color-red-500: #ef4444;
}
@tailwind utilities;
`,
[
// mask-mode
'mask-alpha',
'mask-luminance',
'mask-match-source',
// mask-image
'mask-none',
'mask-linear-to-t',
'mask-linear-to-tr',
'mask-linear-to-r',
'mask-linear-to-br',
'mask-linear-to-b',
'mask-linear-to-bl',
'mask-linear-to-l',
'mask-linear-to-tl',
'mask-[url(/image.png)]',
'mask-[url:--my-url]',
'mask-[linear-gradient(to_bottom,red,blue)]',
'mask-[image:--my-gradient]',
'mask-linear-[125deg]',
'mask-linear-[1.3rad]',
'mask-linear-[to_bottom]',
'-mask-linear-[125deg]',
'-mask-linear-[1.3rad]',
],
),
).toMatchInlineSnapshot(`
":root {
--color-red-500: #ef4444;
}
.mask-\\[image\\:--my-gradient\\] {
background-image: var(--my-gradient);
}
.mask-\\[linear-gradient\\(to_bottom\\,red\\,blue\\)\\] {
background-image: linear-gradient(red, #00f);
}
.mask-\\[url\\(\\/image\\.png\\)\\] {
background-image: url("/image.png");
}
.mask-\\[url\\:--my-url\\] {
background-image: var(--my-url);
}
.-mask-linear-\\[1\\.3rad\\] {
mask-image: linear-gradient(calc(74.4845deg * -1), var(--tw-mask-stops, ));
}
.-mask-linear-\\[125deg\\] {
mask-image: linear-gradient(calc(125deg * -1), var(--tw-mask-stops, ));
}
.mask-alpha {
mask-mode: alpha;
}
.mask-linear-\\[1\\.3rad\\] {
mask-image: linear-gradient(74.4845deg, var(--tw-mask-stops, ));
}
.mask-linear-\\[125deg\\] {
mask-image: linear-gradient(125deg, var(--tw-mask-stops, ));
}
.mask-linear-\\[to_bottom\\], .mask-linear-to-b {
mask-image: linear-gradient(to bottom, var(--tw-mask-stops, ));
}
.mask-linear-to-bl {
mask-image: linear-gradient(to bottom left, var(--tw-mask-stops, ));
}
.mask-linear-to-br {
mask-image: linear-gradient(to bottom right, var(--tw-mask-stops, ));
}
.mask-linear-to-l {
mask-image: linear-gradient(to left, var(--tw-mask-stops, ));
}
.mask-linear-to-r {
mask-image: linear-gradient(to right, var(--tw-mask-stops, ));
}
.mask-linear-to-t {
mask-image: linear-gradient(to top, var(--tw-mask-stops, ));
}
.mask-linear-to-tl {
mask-image: linear-gradient(to top left, var(--tw-mask-stops, ));
}
.mask-linear-to-tr {
mask-image: linear-gradient(to top right, var(--tw-mask-stops, ));
}
.mask-luminance {
mask-mode: luminance;
}
.mask-match-source {
mask-mode: match-source;
}
.mask-none {
mask-image: none;
}"
`)
expect(
run([
'mask',
'mask-unknown',
// mask-mode
'-mask-alpha',
'-mask-luminance',
'-mask-match-source',
// mask-image
'-mask-none',
'-mask-linear-to-t',
'-mask-linear-to-tr',
'-mask-linear-to-r',
'-mask-linear-to-br',
'-mask-linear-to-b',
'-mask-linear-to-bl',
'-mask-linear-to-l',
'-mask-linear-to-tl',
'-mask-[url(/image.png)]',
'-mask-[url:--my-url]',
'-mask-[linear-gradient(to_bottom,red,blue)]',
'-mask-[image:--my-gradient]',
'-mask-linear-[to_bottom]',
]),
).toEqual('')
expect(
compileCss(
css`
@theme {
--opacity-half: 0.5;
--opacity-custom: var(--custom-opacity);
}
@tailwind utilities;
`,
['bg-current/half', 'bg-current/custom'],
),
).toMatchInlineSnapshot(`
":root {
--opacity-half: .5;
--opacity-custom: var(--custom-opacity);
}
.bg-current\\/custom {
background-color: color-mix(in srgb, currentColor var(--opacity-custom, var(--custom-opacity)), transparent);
}
.bg-current\\/half {
background-color: color-mix(in srgb, currentColor var(--opacity-half, .5), transparent);
}"
`)
})
test('from', () => {
expect(
compileCss(

View file

@ -2501,8 +2501,39 @@ export function createUtilities(theme: Theme) {
staticUtility(`bg-linear-to-${value}`, [
['background-image', `linear-gradient(to ${direction}, var(--tw-gradient-stops,))`],
])
staticUtility(`mask-linear-to-${value}`, [
['mask-image', `linear-gradient(to ${direction}, var(--tw-mask-stops,))`],
])
}
staticUtility('mask-none', [['mask-image', 'none']])
staticUtility('mask-alpha', [['mask-mode', 'alpha']])
staticUtility('mask-luminance', [['mask-mode', 'luminance']])
staticUtility('mask-match-source', [['mask-mode', 'match-source']])
utilities.functional('mask-linear', (candidate) => {
if (!candidate.value) return
if (candidate.value.kind === 'arbitrary') {
let value: string | null = candidate.value.value
let type = candidate.value.dataType ?? inferDataType(value, ['angle'])
switch (type) {
case 'angle': {
value = withNegative(value, candidate)
return [decl('mask-image', `linear-gradient(${value}, var(--tw-mask-stops,))`)]
}
default: {
if (candidate.negative) return
return [decl('mask-image', `linear-gradient(${value}, var(--tw-mask-stops,))`)]
}
}
}
})
utilities.functional('bg-linear', (candidate) => {
if (!candidate.value) return
@ -2525,6 +2556,41 @@ export function createUtilities(theme: Theme) {
}
})
utilities.functional('mask', (candidate) => {
if (candidate.negative || !candidate.value) return
// Arbitrary values
if (candidate.value.kind === 'arbitrary') {
let value: string | null = candidate.value.value
let type = candidate.value.dataType ?? inferDataType(value, ['image', 'url'])
switch (type) {
case 'image':
case 'url': {
return [decl('background-image', value)]
}
default: {
return
}
}
}
// `background-image` property
{
let value = theme.resolve(candidate.value.value, ['--background-image'])
if (value) {
return [decl('mask-image', value)]
}
}
})
suggest('mask', () => [
{
values: [],
valueThemeKeys: ['--background-image'],
},
])
utilities.functional('bg', (candidate) => {
if (candidate.negative || !candidate.value) return
@ -2611,11 +2677,31 @@ export function createUtilities(theme: Theme) {
])
}
let maskStopProperties = () => {
return atRoot([
property('--tw-mask-from', '#0000', '<color>'),
property('--tw-mask-to', '#0000', '<color>'),
property('--tw-mask-from', 'transparent', '<color>'),
property('--tw-mask-via', 'transparent', '<color>'),
property('--tw-mask-to', 'transparent', '<color>'),
property('--tw-mask-stops'),
property('--tw-mask-via-stops'),
property('--tw-mask-from-position', '0%', '<length> | <percentage>'),
property('--tw-mask-via-position', '50%', '<length> | <percentage>'),
property('--tw-mask-to-position', '100%', '<length> | <percentage>'),
])
}
type GradientStopDescription = {
color: (value: string) => AstNode[] | undefined
position: (value: string) => AstNode[] | undefined
}
type MaskStopDescription = {
color: (value: string) => AstNode[] | undefined
position: (value: string) => AstNode[] | undefined
}
function gradientStopUtility(classRoot: string, desc: GradientStopDescription) {
utilities.functional(classRoot, (candidate) => {
if (candidate.negative || !candidate.value) return
@ -2676,6 +2762,66 @@ export function createUtilities(theme: Theme) {
])
}
function maskStopUtility(classRoot: string, desc: MaskStopDescription) {
utilities.functional(classRoot, (candidate) => {
if (candidate.negative || !candidate.value) return
// Arbitrary values
if (candidate.value.kind === 'arbitrary') {
let value: string | null = candidate.value.value
let type =
candidate.value.dataType ?? inferDataType(value, ['color', 'length', 'percentage'])
switch (type) {
case 'length':
case 'percentage': {
return desc.position(value)
}
default: {
value = asColor(value, candidate.modifier, theme)
if (value === null) return
return desc.color(value)
}
}
}
// Known values: Color Stops
{
let value = resolveThemeColor(candidate, theme, ['--background-color', '--color'])
if (value) {
return desc.color(value)
}
}
// Known values: Positions
{
let value = theme.resolve(candidate.value.value, ['--gradient-color-stop-positions'])
if (value) {
return desc.position(value)
} else if (
candidate.value.value[candidate.value.value.length - 1] === '%' &&
!Number.isNaN(Number(candidate.value.value.slice(0, -1)))
) {
return desc.position(candidate.value.value)
}
}
})
suggest(classRoot, () => [
{
values: ['current', 'inherit', 'transparent'],
valueThemeKeys: ['--background-color', '--color'],
modifiers: Array.from({ length: 21 }, (_, index) => `${index * 5}`),
modifierThemeKeys: ['--opacity'],
},
{
values: Array.from({ length: 21 }, (_, index) => `${index * 5}%`),
valueThemeKeys: ['--gradient-color-stop-positions'],
},
])
}
gradientStopUtility('from', {
color: (value) => [
gradientStopProperties(),
@ -2715,6 +2861,45 @@ export function createUtilities(theme: Theme) {
position: (value) => [gradientStopProperties(), decl('--tw-gradient-to-position', value)],
})
maskStopUtility('mask-from', {
color: (value) => [
maskStopProperties(),
decl('--tw-sort', '--tw-mask-from'),
decl('--tw-mask-from', value),
decl(
'--tw-mask-stops',
'var(--tw-mask-via-stops, var(--tw-mask-from) var(--tw-mask-from-position), var(--tw-mask-to) var(--tw-mask-to-position))',
),
],
position: (value) => [maskStopProperties(), decl('--tw-mask-from-position', value)],
})
staticUtility('mask-via-none', [['--tw-mask-via-stops', 'initial']])
maskStopUtility('mask-via', {
color: (value) => [
maskStopProperties(),
decl('--tw-sort', '--tw-mask-via'),
decl('--tw-mask-via', value),
decl(
'--tw-mask-via-stops',
'var(--tw-mask-from) var(--tw-mask-from-position), var(--tw-mask-via) var(--tw-mask-via-position), var(--tw-mask-to) var(--tw-mask-to-position)',
),
decl('--tw-mask-stops', 'var(--tw-mask-via-stops)'),
],
position: (value) => [maskStopProperties(), decl('--tw-mask-via-position', value)],
})
maskStopUtility('mask-to', {
color: (value) => [
maskStopProperties(),
decl('--tw-sort', '--tw-mask-to'),
decl('--tw-mask-to', value),
decl(
'--tw-mask-stops',
'var(--tw-mask-via-stops, var(--tw-mask-from) var(--tw-mask-from-position), var(--tw-mask-to) var(--tw-mask-to-position))',
),
],
position: (value) => [maskStopProperties(), decl('--tw-mask-to-position', value)],
})
/**
* @css `box-decoration-break`
*/