Add mask image utilities
This commit is contained in:
parent
7b638868dd
commit
35bf48dffc
2 changed files with 365 additions and 0 deletions
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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`
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue