Add support for “intensity” modifiers on box and text shadows (#17398)

This PR adds support for utilities like:
- `text-shadow-lg/25`

It uses relative color syntax to replace the alpha value of the shadow
from your theme.

When combined with a colors:
- `text-shadow-lg/25 text-shadow-red-500`
- `text-shadow-lg/25 text-shadow-red-500/75`

The alpha values are **multiplied** resulting in a shadow with the color
specified in `--color-red-500` and alpha values of 25% and 18.75%
respectively.
This commit is contained in:
Jordan Pittman 2025-03-28 13:16:00 -04:00 committed by GitHub
parent 5e255deb0e
commit 1a68b99368
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 649 additions and 210 deletions

View File

@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- _Experimental_: Add `text-shadow-*` utilities ([#17389](https://github.com/tailwindlabs/tailwindcss/pull/17389))
- Added new `bg-{top,bottom}-{left,right}` utilities ([#17378](https://github.com/tailwindlabs/tailwindcss/pull/17378))
- Added new `bg-{position,size}-*` utilities for arbitrary values ([#17432](https://github.com/tailwindlabs/tailwindcss/pull/17432))
- Added new `shadow-*/{alpha}`, `inset-shadow-*/{alpha}`, and `text-shadow-*/{alpha}` utilities to control shadow opacity ([#17398](https://github.com/tailwindlabs/tailwindcss/pull/17398))
### Fixed

View File

@ -36,6 +36,12 @@ exports[`compiling CSS > \`@tailwind utilities\` is replaced by utilities using
inherits: false
}
@property --tw-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-inset-shadow {
syntax: "*";
inherits: false;
@ -47,6 +53,12 @@ exports[`compiling CSS > \`@tailwind utilities\` is replaced by utilities using
inherits: false
}
@property --tw-inset-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-ring-color {
syntax: "*";
inherits: false

View File

@ -4672,6 +4672,27 @@ exports[`getClassList 1`] = `
"inset-ring-transparent/95",
"inset-ring-transparent/100",
"inset-shadow",
"inset-shadow/0",
"inset-shadow/5",
"inset-shadow/10",
"inset-shadow/15",
"inset-shadow/20",
"inset-shadow/25",
"inset-shadow/30",
"inset-shadow/35",
"inset-shadow/40",
"inset-shadow/45",
"inset-shadow/50",
"inset-shadow/55",
"inset-shadow/60",
"inset-shadow/65",
"inset-shadow/70",
"inset-shadow/75",
"inset-shadow/80",
"inset-shadow/85",
"inset-shadow/90",
"inset-shadow/95",
"inset-shadow/100",
"inset-shadow-current",
"inset-shadow-current/0",
"inset-shadow-current/5",
@ -4717,7 +4738,29 @@ exports[`getClassList 1`] = `
"inset-shadow-inherit/95",
"inset-shadow-inherit/100",
"inset-shadow-initial",
"inset-shadow-none",
"inset-shadow-sm",
"inset-shadow-sm/0",
"inset-shadow-sm/5",
"inset-shadow-sm/10",
"inset-shadow-sm/15",
"inset-shadow-sm/20",
"inset-shadow-sm/25",
"inset-shadow-sm/30",
"inset-shadow-sm/35",
"inset-shadow-sm/40",
"inset-shadow-sm/45",
"inset-shadow-sm/50",
"inset-shadow-sm/55",
"inset-shadow-sm/60",
"inset-shadow-sm/65",
"inset-shadow-sm/70",
"inset-shadow-sm/75",
"inset-shadow-sm/80",
"inset-shadow-sm/85",
"inset-shadow-sm/90",
"inset-shadow-sm/95",
"inset-shadow-sm/100",
"inset-shadow-transparent",
"inset-shadow-transparent/0",
"inset-shadow-transparent/5",
@ -7306,6 +7349,27 @@ exports[`getClassList 1`] = `
"sepia-50",
"sepia-100",
"shadow",
"shadow/0",
"shadow/5",
"shadow/10",
"shadow/15",
"shadow/20",
"shadow/25",
"shadow/30",
"shadow/35",
"shadow/40",
"shadow/45",
"shadow/50",
"shadow/55",
"shadow/60",
"shadow/65",
"shadow/70",
"shadow/75",
"shadow/80",
"shadow/85",
"shadow/90",
"shadow/95",
"shadow/100",
"shadow-current",
"shadow-current/0",
"shadow-current/5",
@ -7757,6 +7821,27 @@ exports[`getClassList 1`] = `
"text-pretty",
"text-right",
"text-shadow",
"text-shadow/0",
"text-shadow/5",
"text-shadow/10",
"text-shadow/15",
"text-shadow/20",
"text-shadow/25",
"text-shadow/30",
"text-shadow/35",
"text-shadow/40",
"text-shadow/45",
"text-shadow/50",
"text-shadow/55",
"text-shadow/60",
"text-shadow/65",
"text-shadow/70",
"text-shadow/75",
"text-shadow/80",
"text-shadow/85",
"text-shadow/90",
"text-shadow/95",
"text-shadow/100",
"text-shadow-current",
"text-shadow-current/0",
"text-shadow-current/5",

View File

@ -1499,6 +1499,12 @@ describe('Parsing theme values from CSS', () => {
inherits: false
}
@property --tw-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-inset-shadow {
syntax: "*";
inherits: false;
@ -1510,6 +1516,12 @@ describe('Parsing theme values from CSS', () => {
inherits: false
}
@property --tw-inset-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-ring-color {
syntax: "*";
inherits: false

View File

@ -198,6 +198,12 @@ test('inset', async () => {
inherits: false
}
@property --tw-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-inset-shadow {
syntax: "*";
inherits: false;
@ -209,6 +215,12 @@ test('inset', async () => {
inherits: false
}
@property --tw-inset-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-ring-color {
syntax: "*";
inherits: false
@ -15453,6 +15465,10 @@ test('text-shadow', async () => {
'text-shadow-[var(--value)]',
'text-shadow-[shadow:var(--value)]',
'text-shadow-sm/25',
'text-shadow-[12px_12px_#0088cc]/25',
'text-shadow-[10px_10px]/25',
// Colors
'text-shadow-red-500',
'text-shadow-red-500/50',
@ -15482,16 +15498,31 @@ test('text-shadow', async () => {
--color-red-500: #ef4444;
}
.text-shadow-\\[10px_10px\\]\\/25 {
--tw-text-shadow-alpha: 25%;
text-shadow: 10px 10px var(--tw-text-shadow-color, oklab(from currentcolor l a b / 25%));
}
.text-shadow-\\[12px_12px_\\#0088cc\\]\\/25 {
--tw-text-shadow-alpha: 25%;
text-shadow: 12px 12px var(--tw-text-shadow-color, oklab(59.9824% -.06725 -.12414 / .25));
}
.text-shadow-sm\\/25 {
--tw-text-shadow-alpha: 25%;
text-shadow: 0px 1px 2px var(--tw-text-shadow-color, oklab(0% 0 0 / .25)), 0px 2px 2px var(--tw-text-shadow-color, oklab(0% 0 0 / .25));
}
.text-shadow-2xs {
text-shadow: 0px 1px 0px var(--tw-text-shadow-color, #0000001a);
}
.text-shadow-\\[\\#0088cc\\] {
--tw-text-shadow-color: #08c;
--tw-text-shadow-color: color-mix(in oklab, #08c var(--tw-text-shadow-alpha), transparent);
}
.text-shadow-\\[\\#0088cc\\]\\/50, .text-shadow-\\[\\#0088cc\\]\\/\\[0\\.5\\], .text-shadow-\\[\\#0088cc\\]\\/\\[50\\%\\] {
--tw-text-shadow-color: oklab(59.9824% -.06725 -.12414 / .5);
--tw-text-shadow-color: color-mix(in oklab, oklab(59.9824% -.06725 -.12414 / .5) var(--tw-text-shadow-alpha), transparent);
}
.text-shadow-\\[10px_10px\\] {
@ -15503,11 +15534,11 @@ test('text-shadow', async () => {
}
.text-shadow-\\[color\\:var\\(--value\\)\\] {
--tw-text-shadow-color: var(--value);
--tw-text-shadow-color: color-mix(in oklab, var(--value) var(--tw-text-shadow-alpha), transparent);
}
.text-shadow-\\[color\\:var\\(--value\\)\\]\\/50, .text-shadow-\\[color\\:var\\(--value\\)\\]\\/\\[0\\.5\\], .text-shadow-\\[color\\:var\\(--value\\)\\]\\/\\[50\\%\\] {
--tw-text-shadow-color: color-mix(in oklab, var(--value) 50%, transparent);
--tw-text-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--value) 50%, transparent) var(--tw-text-shadow-alpha), transparent);
}
.text-shadow-\\[shadow\\:var\\(--value\\)\\], .text-shadow-\\[var\\(--value\\)\\] {
@ -15515,15 +15546,15 @@ test('text-shadow', async () => {
}
.text-shadow-current {
--tw-text-shadow-color: currentColor;
--tw-text-shadow-color: color-mix(in oklab, currentColor var(--tw-text-shadow-alpha), transparent);
}
.text-shadow-current\\/50, .text-shadow-current\\/\\[0\\.5\\], .text-shadow-current\\/\\[50\\%\\] {
--tw-text-shadow-color: color-mix(in oklab, currentColor 50%, transparent);
--tw-text-shadow-color: color-mix(in oklab, color-mix(in oklab, currentColor 50%, transparent) var(--tw-text-shadow-alpha), transparent);
}
.text-shadow-inherit {
--tw-text-shadow-color: inherit;
--tw-text-shadow-color: color-mix(in oklab, inherit var(--tw-text-shadow-alpha), transparent);
}
.text-shadow-none {
@ -15531,23 +15562,23 @@ test('text-shadow', async () => {
}
.text-shadow-red-500 {
--tw-text-shadow-color: var(--color-red-500);
--tw-text-shadow-color: color-mix(in oklab, var(--color-red-500) var(--tw-text-shadow-alpha), transparent);
}
.text-shadow-red-500\\/2\\.5 {
--tw-text-shadow-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent);
--tw-text-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-red-500) 2.5%, transparent) var(--tw-text-shadow-alpha), transparent);
}
.text-shadow-red-500\\/2\\.25 {
--tw-text-shadow-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent);
--tw-text-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-red-500) 2.25%, transparent) var(--tw-text-shadow-alpha), transparent);
}
.text-shadow-red-500\\/2\\.75 {
--tw-text-shadow-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent);
--tw-text-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-red-500) 2.75%, transparent) var(--tw-text-shadow-alpha), transparent);
}
.text-shadow-red-500\\/50, .text-shadow-red-500\\/\\[0\\.5\\], .text-shadow-red-500\\/\\[50\\%\\] {
--tw-text-shadow-color: color-mix(in oklab, var(--color-red-500) 50%, transparent);
--tw-text-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-red-500) 50%, transparent) var(--tw-text-shadow-alpha), transparent);
}
.text-shadow-sm {
@ -15555,12 +15586,18 @@ test('text-shadow', async () => {
}
.text-shadow-transparent {
--tw-text-shadow-color: transparent;
--tw-text-shadow-color: color-mix(in oklab, transparent var(--tw-text-shadow-alpha), transparent);
}
@property --tw-text-shadow-color {
syntax: "*";
inherits: false
}
@property --tw-text-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}"
`)
expect(
@ -15607,6 +15644,10 @@ test('shadow', async () => {
'shadow-[var(--value)]',
'shadow-[shadow:var(--value)]',
'shadow-sm/25',
'shadow-[12px_12px_#0088cc]/25',
'shadow-[10px_10px]/25',
// Colors
'shadow-red-500',
'shadow-red-500/50',
@ -15636,6 +15677,24 @@ test('shadow', async () => {
--color-red-500: #ef4444;
}
.shadow-\\[10px_10px\\]\\/25 {
--tw-shadow-alpha: 25%;
--tw-shadow: 10px 10px var(--tw-shadow-color, oklab(from currentcolor l a b / 25%));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.shadow-\\[12px_12px_\\#0088cc\\]\\/25 {
--tw-shadow-alpha: 25%;
--tw-shadow: 12px 12px var(--tw-shadow-color, oklab(59.9824% -.06725 -.12414 / .25));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.shadow-sm\\/25 {
--tw-shadow-alpha: 25%;
--tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, oklab(0% 0 0 / .25)), 0 1px 2px -1px var(--tw-shadow-color, oklab(0% 0 0 / .25));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.shadow-\\[10px_10px\\] {
--tw-shadow: 10px 10px var(--tw-shadow-color, currentcolor);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
@ -15667,55 +15726,55 @@ test('shadow', async () => {
}
.shadow-\\[\\#0088cc\\] {
--tw-shadow-color: #08c;
--tw-shadow-color: color-mix(in oklab, #08c var(--tw-shadow-alpha), transparent);
}
.shadow-\\[\\#0088cc\\]\\/50, .shadow-\\[\\#0088cc\\]\\/\\[0\\.5\\], .shadow-\\[\\#0088cc\\]\\/\\[50\\%\\] {
--tw-shadow-color: oklab(59.9824% -.06725 -.12414 / .5);
--tw-shadow-color: color-mix(in oklab, oklab(59.9824% -.06725 -.12414 / .5) var(--tw-shadow-alpha), transparent);
}
.shadow-\\[color\\:var\\(--value\\)\\] {
--tw-shadow-color: var(--value);
--tw-shadow-color: color-mix(in oklab, var(--value) var(--tw-shadow-alpha), transparent);
}
.shadow-\\[color\\:var\\(--value\\)\\]\\/50, .shadow-\\[color\\:var\\(--value\\)\\]\\/\\[0\\.5\\], .shadow-\\[color\\:var\\(--value\\)\\]\\/\\[50\\%\\] {
--tw-shadow-color: color-mix(in oklab, var(--value) 50%, transparent);
--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--value) 50%, transparent) var(--tw-shadow-alpha), transparent);
}
.shadow-current {
--tw-shadow-color: currentColor;
--tw-shadow-color: color-mix(in oklab, currentColor var(--tw-shadow-alpha), transparent);
}
.shadow-current\\/50, .shadow-current\\/\\[0\\.5\\], .shadow-current\\/\\[50\\%\\] {
--tw-shadow-color: color-mix(in oklab, currentColor 50%, transparent);
--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, currentColor 50%, transparent) var(--tw-shadow-alpha), transparent);
}
.shadow-inherit {
--tw-shadow-color: inherit;
--tw-shadow-color: color-mix(in oklab, inherit var(--tw-shadow-alpha), transparent);
}
.shadow-red-500 {
--tw-shadow-color: var(--color-red-500);
--tw-shadow-color: color-mix(in oklab, var(--color-red-500) var(--tw-shadow-alpha), transparent);
}
.shadow-red-500\\/2\\.5 {
--tw-shadow-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent);
--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-red-500) 2.5%, transparent) var(--tw-shadow-alpha), transparent);
}
.shadow-red-500\\/2\\.25 {
--tw-shadow-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent);
--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-red-500) 2.25%, transparent) var(--tw-shadow-alpha), transparent);
}
.shadow-red-500\\/2\\.75 {
--tw-shadow-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent);
--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-red-500) 2.75%, transparent) var(--tw-shadow-alpha), transparent);
}
.shadow-red-500\\/50, .shadow-red-500\\/\\[0\\.5\\], .shadow-red-500\\/\\[50\\%\\] {
--tw-shadow-color: color-mix(in oklab, var(--color-red-500) 50%, transparent);
--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-red-500) 50%, transparent) var(--tw-shadow-alpha), transparent);
}
.shadow-transparent {
--tw-shadow-color: transparent;
--tw-shadow-color: color-mix(in oklab, transparent var(--tw-shadow-alpha), transparent);
}
@property --tw-shadow {
@ -15729,6 +15788,12 @@ test('shadow', async () => {
inherits: false
}
@property --tw-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-inset-shadow {
syntax: "*";
inherits: false;
@ -15740,6 +15805,12 @@ test('shadow', async () => {
inherits: false
}
@property --tw-inset-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-ring-color {
syntax: "*";
inherits: false
@ -15829,6 +15900,10 @@ test('inset-shadow', async () => {
'inset-shadow-[var(--value)]',
'inset-shadow-[shadow:var(--value)]',
'inset-shadow-sm/25',
'inset-shadow-[12px_12px_#0088cc]/25',
'inset-shadow-[10px_10px]/25',
// Colors
'inset-shadow-red-500',
'inset-shadow-red-500/50',
@ -15858,6 +15933,24 @@ test('inset-shadow', async () => {
--color-red-500: #ef4444;
}
.inset-shadow-\\[10px_10px\\]\\/25 {
--tw-inset-shadow-alpha: 25%;
--tw-inset-shadow: inset 10px 10px var(--tw-inset-shadow-color, oklab(from currentcolor l a b / 25%));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.inset-shadow-\\[12px_12px_\\#0088cc\\]\\/25 {
--tw-inset-shadow-alpha: 25%;
--tw-inset-shadow: inset 12px 12px var(--tw-inset-shadow-color, oklab(59.9824% -.06725 -.12414 / .25));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.inset-shadow-sm\\/25 {
--tw-inset-shadow-alpha: 25%;
--tw-inset-shadow: inset 0 1px 1px var(--tw-inset-shadow-color, oklab(0% 0 0 / .25));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
}
.inset-shadow {
--tw-inset-shadow: inset 0 2px 4px var(--tw-inset-shadow-color, #0000000d);
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
@ -15889,55 +15982,55 @@ test('inset-shadow', async () => {
}
.inset-shadow-\\[\\#0088cc\\] {
--tw-inset-shadow-color: #08c;
--tw-inset-shadow-color: color-mix(in oklab, #08c var(--tw-inset-shadow-alpha), transparent);
}
.inset-shadow-\\[\\#0088cc\\]\\/50, .inset-shadow-\\[\\#0088cc\\]\\/\\[0\\.5\\], .inset-shadow-\\[\\#0088cc\\]\\/\\[50\\%\\] {
--tw-inset-shadow-color: oklab(59.9824% -.06725 -.12414 / .5);
--tw-inset-shadow-color: color-mix(in oklab, oklab(59.9824% -.06725 -.12414 / .5) var(--tw-inset-shadow-alpha), transparent);
}
.inset-shadow-\\[color\\:var\\(--value\\)\\] {
--tw-inset-shadow-color: var(--value);
--tw-inset-shadow-color: color-mix(in oklab, var(--value) var(--tw-inset-shadow-alpha), transparent);
}
.inset-shadow-\\[color\\:var\\(--value\\)\\]\\/50, .inset-shadow-\\[color\\:var\\(--value\\)\\]\\/\\[0\\.5\\], .inset-shadow-\\[color\\:var\\(--value\\)\\]\\/\\[50\\%\\] {
--tw-inset-shadow-color: color-mix(in oklab, var(--value) 50%, transparent);
--tw-inset-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--value) 50%, transparent) var(--tw-inset-shadow-alpha), transparent);
}
.inset-shadow-current {
--tw-inset-shadow-color: currentColor;
--tw-inset-shadow-color: color-mix(in oklab, currentColor var(--tw-inset-shadow-alpha), transparent);
}
.inset-shadow-current\\/50, .inset-shadow-current\\/\\[0\\.5\\], .inset-shadow-current\\/\\[50\\%\\] {
--tw-inset-shadow-color: color-mix(in oklab, currentColor 50%, transparent);
--tw-inset-shadow-color: color-mix(in oklab, color-mix(in oklab, currentColor 50%, transparent) var(--tw-inset-shadow-alpha), transparent);
}
.inset-shadow-inherit {
--tw-inset-shadow-color: inherit;
--tw-inset-shadow-color: color-mix(in oklab, inherit var(--tw-inset-shadow-alpha), transparent);
}
.inset-shadow-red-500 {
--tw-inset-shadow-color: var(--color-red-500);
--tw-inset-shadow-color: color-mix(in oklab, var(--color-red-500) var(--tw-inset-shadow-alpha), transparent);
}
.inset-shadow-red-500\\/2\\.5 {
--tw-inset-shadow-color: color-mix(in oklab, var(--color-red-500) 2.5%, transparent);
--tw-inset-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-red-500) 2.5%, transparent) var(--tw-inset-shadow-alpha), transparent);
}
.inset-shadow-red-500\\/2\\.25 {
--tw-inset-shadow-color: color-mix(in oklab, var(--color-red-500) 2.25%, transparent);
--tw-inset-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-red-500) 2.25%, transparent) var(--tw-inset-shadow-alpha), transparent);
}
.inset-shadow-red-500\\/2\\.75 {
--tw-inset-shadow-color: color-mix(in oklab, var(--color-red-500) 2.75%, transparent);
--tw-inset-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-red-500) 2.75%, transparent) var(--tw-inset-shadow-alpha), transparent);
}
.inset-shadow-red-500\\/50, .inset-shadow-red-500\\/\\[0\\.5\\], .inset-shadow-red-500\\/\\[50\\%\\] {
--tw-inset-shadow-color: color-mix(in oklab, var(--color-red-500) 50%, transparent);
--tw-inset-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-red-500) 50%, transparent) var(--tw-inset-shadow-alpha), transparent);
}
.inset-shadow-transparent {
--tw-inset-shadow-color: transparent;
--tw-inset-shadow-color: color-mix(in oklab, transparent var(--tw-inset-shadow-alpha), transparent);
}
@property --tw-shadow {
@ -15951,6 +16044,12 @@ test('inset-shadow', async () => {
inherits: false
}
@property --tw-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-inset-shadow {
syntax: "*";
inherits: false;
@ -15962,6 +16061,12 @@ test('inset-shadow', async () => {
inherits: false
}
@property --tw-inset-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-ring-color {
syntax: "*";
inherits: false
@ -16193,6 +16298,12 @@ test('ring', async () => {
inherits: false
}
@property --tw-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-inset-shadow {
syntax: "*";
inherits: false;
@ -16204,6 +16315,12 @@ test('ring', async () => {
inherits: false
}
@property --tw-inset-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-ring-color {
syntax: "*";
inherits: false
@ -16276,6 +16393,12 @@ test('ring', async () => {
inherits: false
}
@property --tw-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-inset-shadow {
syntax: "*";
inherits: false;
@ -16287,6 +16410,12 @@ test('ring', async () => {
inherits: false
}
@property --tw-inset-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-ring-color {
syntax: "*";
inherits: false
@ -16528,6 +16657,12 @@ test('inset-ring', async () => {
inherits: false
}
@property --tw-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-inset-shadow {
syntax: "*";
inherits: false;
@ -16539,6 +16674,12 @@ test('inset-ring', async () => {
inherits: false
}
@property --tw-inset-shadow-alpha {
syntax: "<percentage>";
inherits: false;
initial-value: 100%;
}
@property --tw-ring-color {
syntax: "*";
inherits: false

View File

@ -181,6 +181,23 @@ export function withAlpha(value: string, alpha: string): string {
return `color-mix(in oklab, ${value} ${alpha}, transparent)`
}
/**
* Apply opacity to a color using `color-mix`.
*/
export function replaceAlpha(value: string, alpha: string | null): string {
if (alpha === null) return value
// Convert numeric values (like `0.5`) to percentages (like `50%`) so they
// work properly with `color-mix`. Assume anything that isn't a number is
// safe to pass through as-is, like `var(--my-opacity)`.
let alphaAsNumber = Number(alpha)
if (!Number.isNaN(alphaAsNumber)) {
alpha = `${alphaAsNumber * 100}%`
}
return `oklab(from ${value} l a b / ${alpha})`
}
/**
* Resolve a color value + optional opacity modifier to a final color.
*/
@ -4261,7 +4278,10 @@ export function createUtilities(theme: Theme) {
if (enableTextShadows) {
let textShadowProperties = () => {
return atRoot([property('--tw-text-shadow-color')])
return atRoot([
property('--tw-text-shadow-color'),
property('--tw-text-shadow-alpha', '100%', '<percentage>'),
])
}
staticUtility('text-shadow-initial', [
@ -4270,15 +4290,28 @@ export function createUtilities(theme: Theme) {
])
utilities.functional('text-shadow', (candidate) => {
let alpha: string | undefined
if (candidate.modifier) {
if (candidate.modifier.kind === 'arbitrary') {
alpha = candidate.modifier.value
} else {
if (isPositiveInteger(candidate.modifier.value)) {
alpha = `${candidate.modifier.value}%`
}
}
}
if (!candidate.value) {
let value = theme.get(['--text-shadow'])
if (value === null) return
return [
textShadowProperties(),
decl('--tw-text-shadow-alpha', alpha),
decl(
'text-shadow',
replaceShadowColors(value, (color) => `var(--tw-text-shadow-color, ${color})`),
replaceShadowColors(value, alpha, (color) => `var(--tw-text-shadow-color, ${color})`),
),
]
}
@ -4291,15 +4324,22 @@ export function createUtilities(theme: Theme) {
case 'color': {
value = asColor(value, candidate.modifier, theme)
if (value === null) return
return [textShadowProperties(), decl('--tw-text-shadow-color', value)]
return [
textShadowProperties(),
decl('--tw-text-shadow-color', withAlpha(value, 'var(--tw-text-shadow-alpha)')),
]
}
default: {
return [
textShadowProperties(),
decl('--tw-text-shadow-alpha', alpha),
decl(
'text-shadow',
replaceShadowColors(value, (color) => `var(--tw-text-shadow-color, ${color})`),
replaceShadowColors(
value,
alpha,
(color) => `var(--tw-text-shadow-color, ${color})`,
),
),
]
}
@ -4316,12 +4356,12 @@ export function createUtilities(theme: Theme) {
{
let value = theme.get([`--text-shadow-${candidate.value.value}`])
if (value) {
if (candidate.modifier) return
return [
textShadowProperties(),
decl('--tw-text-shadow-alpha', alpha),
decl(
'text-shadow',
replaceShadowColors(value, (color) => `var(--tw-text-shadow-color, ${color})`),
replaceShadowColors(value, alpha, (color) => `var(--tw-text-shadow-color, ${color})`),
),
]
}
@ -4331,7 +4371,10 @@ export function createUtilities(theme: Theme) {
{
let value = resolveThemeColor(candidate, theme, ['--text-shadow-color', '--color'])
if (value) {
return [textShadowProperties(), decl('--tw-text-shadow-color', value)]
return [
textShadowProperties(),
decl('--tw-text-shadow-color', withAlpha(value, 'var(--tw-text-shadow-alpha)')),
]
}
}
})
@ -4344,7 +4387,10 @@ export function createUtilities(theme: Theme) {
},
{
values: ['none'],
},
{
valueThemeKeys: ['--text-shadow'],
modifiers: Array.from({ length: 21 }, (_, index) => `${index * 5}`),
hasDefaultValue: true,
},
])
@ -4364,8 +4410,10 @@ export function createUtilities(theme: Theme) {
return atRoot([
property('--tw-shadow', nullShadow),
property('--tw-shadow-color'),
property('--tw-shadow-alpha', '100%', '<percentage>'),
property('--tw-inset-shadow', nullShadow),
property('--tw-inset-shadow-color'),
property('--tw-inset-shadow-alpha', '100%', '<percentage>'),
property('--tw-ring-color'),
property('--tw-ring-shadow', nullShadow),
property('--tw-inset-ring-color'),
@ -4382,15 +4430,28 @@ export function createUtilities(theme: Theme) {
staticUtility('shadow-initial', [boxShadowProperties, ['--tw-shadow-color', 'initial']])
utilities.functional('shadow', (candidate) => {
let alpha: string | undefined
if (candidate.modifier) {
if (candidate.modifier.kind === 'arbitrary') {
alpha = candidate.modifier.value
} else {
if (isPositiveInteger(candidate.modifier.value)) {
alpha = `${candidate.modifier.value}%`
}
}
}
if (!candidate.value) {
let value = theme.get(['--shadow'])
if (value === null) return
return [
boxShadowProperties(),
decl('--tw-shadow-alpha', alpha),
decl(
'--tw-shadow',
replaceShadowColors(value, (color) => `var(--tw-shadow-color, ${color})`),
replaceShadowColors(value, alpha, (color) => `var(--tw-shadow-color, ${color})`),
),
decl('box-shadow', cssBoxShadowValue),
]
@ -4405,14 +4466,18 @@ export function createUtilities(theme: Theme) {
value = asColor(value, candidate.modifier, theme)
if (value === null) return
return [boxShadowProperties(), decl('--tw-shadow-color', value)]
return [
boxShadowProperties(),
decl('--tw-shadow-color', withAlpha(value, 'var(--tw-shadow-alpha)')),
]
}
default: {
return [
boxShadowProperties(),
decl('--tw-shadow-alpha', alpha),
decl(
'--tw-shadow',
replaceShadowColors(value, (color) => `var(--tw-shadow-color, ${color})`),
replaceShadowColors(value, alpha, (color) => `var(--tw-shadow-color, ${color})`),
),
decl('box-shadow', cssBoxShadowValue),
]
@ -4434,12 +4499,12 @@ export function createUtilities(theme: Theme) {
{
let value = theme.get([`--shadow-${candidate.value.value}`])
if (value) {
if (candidate.modifier) return
return [
boxShadowProperties(),
decl('--tw-shadow-alpha', alpha),
decl(
'--tw-shadow',
replaceShadowColors(value, (color) => `var(--tw-shadow-color, ${color})`),
replaceShadowColors(value, alpha, (color) => `var(--tw-shadow-color, ${color})`),
),
decl('box-shadow', cssBoxShadowValue),
]
@ -4450,7 +4515,10 @@ export function createUtilities(theme: Theme) {
{
let value = resolveThemeColor(candidate, theme, ['--box-shadow-color', '--color'])
if (value) {
return [boxShadowProperties(), decl('--tw-shadow-color', value)]
return [
boxShadowProperties(),
decl('--tw-shadow-color', withAlpha(value, 'var(--tw-shadow-alpha)')),
]
}
}
})
@ -4463,7 +4531,10 @@ export function createUtilities(theme: Theme) {
},
{
values: ['none'],
},
{
valueThemeKeys: ['--shadow'],
modifiers: Array.from({ length: 21 }, (_, index) => `${index * 5}`),
hasDefaultValue: true,
},
])
@ -4474,15 +4545,28 @@ export function createUtilities(theme: Theme) {
])
utilities.functional('inset-shadow', (candidate) => {
let alpha: string | undefined
if (candidate.modifier) {
if (candidate.modifier.kind === 'arbitrary') {
alpha = candidate.modifier.value
} else {
if (isPositiveInteger(candidate.modifier.value)) {
alpha = `${candidate.modifier.value}%`
}
}
}
if (!candidate.value) {
let value = theme.get(['--inset-shadow'])
if (value === null) return
return [
boxShadowProperties(),
decl('--tw-inset-shadow-alpha', alpha),
decl(
'--tw-inset-shadow',
replaceShadowColors(value, (color) => `var(--tw-inset-shadow-color, ${color})`),
replaceShadowColors(value, alpha, (color) => `var(--tw-inset-shadow-color, ${color})`),
),
decl('box-shadow', cssBoxShadowValue),
]
@ -4497,14 +4581,22 @@ export function createUtilities(theme: Theme) {
value = asColor(value, candidate.modifier, theme)
if (value === null) return
return [boxShadowProperties(), decl('--tw-inset-shadow-color', value)]
return [
boxShadowProperties(),
decl('--tw-inset-shadow-color', withAlpha(value, 'var(--tw-inset-shadow-alpha)')),
]
}
default: {
return [
boxShadowProperties(),
decl('--tw-inset-shadow-alpha', alpha),
decl(
'--tw-inset-shadow',
`inset ${replaceShadowColors(value, (color) => `var(--tw-inset-shadow-color, ${color})`)}`,
`inset ${replaceShadowColors(
value,
alpha,
(color) => `var(--tw-inset-shadow-color, ${color})`,
)}`,
),
decl('box-shadow', cssBoxShadowValue),
]
@ -4527,12 +4619,16 @@ export function createUtilities(theme: Theme) {
let value = theme.get([`--inset-shadow-${candidate.value.value}`])
if (value) {
if (candidate.modifier) return
return [
boxShadowProperties(),
decl('--tw-inset-shadow-alpha', alpha),
decl(
'--tw-inset-shadow',
replaceShadowColors(value, (color) => `var(--tw-inset-shadow-color, ${color})`),
replaceShadowColors(
value,
alpha,
(color) => `var(--tw-inset-shadow-color, ${color})`,
),
),
decl('box-shadow', cssBoxShadowValue),
]
@ -4543,7 +4639,10 @@ export function createUtilities(theme: Theme) {
{
let value = resolveThemeColor(candidate, theme, ['--box-shadow-color', '--color'])
if (value) {
return [boxShadowProperties(), decl('--tw-inset-shadow-color', value)]
return [
boxShadowProperties(),
decl('--tw-inset-shadow-color', withAlpha(value, 'var(--tw-inset-shadow-alpha)')),
]
}
}
})
@ -4555,8 +4654,11 @@ export function createUtilities(theme: Theme) {
modifiers: Array.from({ length: 21 }, (_, index) => `${index * 5}`),
},
{
values: [],
values: ['none'],
},
{
valueThemeKeys: ['--inset-shadow'],
modifiers: Array.from({ length: 21 }, (_, index) => `${index * 5}`),
hasDefaultValue: true,
},
])

View File

@ -38,12 +38,35 @@ const table = [
'0 0 1px var(--tw-shadow-color, var(--my-color))',
].join(', '),
},
{
input: '1px 1px var(--my-color)',
intensity: '50%',
output: '1px 1px var(--tw-shadow-color, oklab(from var(--my-color) l a b / 50%))',
},
{
input: '1px 2px 3px 4px',
intensity: '50%',
output: '1px 2px 3px 4px var(--tw-shadow-color, oklab(from currentcolor l a b / 50%))',
},
{
input: ['var(--my-shadow)', '1px 1px var(--my-color)', '0 0 1px var(--my-color)'].join(', '),
intensity: '50%',
output: [
'var(--my-shadow)',
'1px 1px var(--tw-shadow-color, oklab(from var(--my-color) l a b / 50%))',
'0 0 1px var(--tw-shadow-color, oklab(from var(--my-color) l a b / 50%))',
].join(', '),
},
]
it.each(table)(
'should replace the color of box-shadow $input with $output',
({ input, output }) => {
let parsed = replaceShadowColors(input, (color) => `var(--tw-shadow-color, ${color})`)
({ input, intensity = null, output }) => {
let parsed = replaceShadowColors(
input,
intensity,
(color) => `var(--tw-shadow-color, ${color})`,
)
expect(parsed).toEqual(output)
},
)

View File

@ -1,9 +1,14 @@
import { replaceAlpha } from '../utilities'
import { segment } from './segment'
const KEYWORDS = new Set(['inset', 'inherit', 'initial', 'revert', 'unset'])
const LENGTH = /^-?(\d+|\.\d+)(.*?)$/g
export function replaceShadowColors(input: string, replacement: (color: string) => string) {
export function replaceShadowColors(
input: string,
intensity: string | null | undefined,
replacement: (color: string) => string,
) {
let shadows = segment(input, ',').map((shadow) => {
shadow = shadow.trim()
let parts = segment(shadow, ' ').filter((part) => part.trim() !== '')
@ -33,7 +38,7 @@ export function replaceShadowColors(input: string, replacement: (color: string)
// we can't know what to replace.
if (offsetX === null || offsetY === null) return shadow
let replacementColor = replacement(color ?? 'currentcolor')
let replacementColor = replacement(replaceAlpha(color ?? 'currentcolor', intensity ?? null))
if (color !== null) {
// If a color was found, replace the color.

View File

@ -4,6 +4,7 @@ import fs from 'node:fs'
import path from 'node:path'
import { compile } from '../src'
import { optimizeCss } from '../src/test-utils/run'
import { segment } from '../src/utils/segment'
const html = String.raw
const css = String.raw
@ -214,7 +215,7 @@ test.skip("::file-selector-button can receive a border with just the 'border' ut
})
test('composing shadow, inset shadow, ring, and inset ring', async ({ page }) => {
let { getPropertyValue } = await render(
let { getPropertyList } = await render(
page,
html`<div
id="x"
@ -222,19 +223,19 @@ test('composing shadow, inset shadow, ring, and inset ring', async ({ page }) =>
></div>`,
)
expect(await getPropertyValue('#x', 'box-shadow')).toEqual(
[
'rgb(0, 255, 0) 0px 2px 4px 0px inset', // inset-shadow
'rgb(0, 0, 255) 0px 0px 0px 1px inset', // inset-ring
'rgba(0, 0, 0, 0) 0px 0px 0px 0px', // ring-offset (disabled)
'rgb(255, 255, 255) 0px 0px 0px 1px', // ring
'rgb(255, 0, 0) 0px 1px 3px 0px, rgb(255, 0, 0) 0px 1px 2px -1px', // shadow
].join(', '),
)
expect(await getPropertyList('#x', 'box-shadow')).toEqual([
expect.stringMatching(/oklab\(0.866\d+ -0.233\d+ 0.179\d+\) 0px 2px 4px 0px inset/), // inset-shadow
'rgb(0, 0, 255) 0px 0px 0px 1px inset', // inset-ring
'rgba(0, 0, 0, 0) 0px 0px 0px 0px', // ring-offset (disabled)
'rgb(255, 255, 255) 0px 0px 0px 1px', // ring
expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 1px 3px 0px/), // shadow
expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 1px 2px -1px/), // shadow
])
})
test('shadow colors', async ({ page }) => {
let { getPropertyValue } = await render(
let { getPropertyList } = await render(
page,
html`
<div id="a" class="shadow-sm shadow-red"></div>
@ -244,84 +245,111 @@ test('shadow colors', async ({ page }) => {
<div id="e" class="shadow-sm shadow-red hover:shadow-xl hover:shadow-initial">
Hello world
</div>
<div id="f" class="shadow-xs/75">Hello world</div>
<div id="g" class="shadow-xs/75 shadow-red/75">Hello world</div>
`,
)
expect(await getPropertyValue('#a', 'box-shadow')).toEqual(
[
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgb(255, 0, 0) 0px 1px 3px 0px, rgb(255, 0, 0) 0px 1px 2px -1px',
].join(', '),
)
expect(await getPropertyValue('#b', 'box-shadow')).toEqual(
[
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgb(255, 0, 0) 0px 20px 25px -5px, rgb(255, 0, 0) 0px 8px 10px -6px',
].join(', '),
)
expect(await getPropertyValue('#c', 'box-shadow')).toEqual(
[
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgb(255, 0, 0) 0px 2px 4px 0px',
].join(', '),
)
expect(await getPropertyList('#a', 'box-shadow')).toEqual([
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
expect(await getPropertyValue('#d', 'box-shadow')).toEqual(
[
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgb(255, 0, 0) 0px 1px 3px 0px, rgb(255, 0, 0) 0px 1px 2px -1px',
].join(', '),
)
//
expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 1px 3px 0px/),
expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 1px 2px -1px/),
])
expect(await getPropertyList('#b', 'box-shadow')).toEqual([
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
//
expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 20px 25px -5px/),
expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 8px 10px -6px/),
])
expect(await getPropertyList('#c', 'box-shadow')).toEqual([
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
//
expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 2px 4px 0px/),
])
expect(await getPropertyList('#d', 'box-shadow')).toEqual([
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
//
expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 1px 3px 0px/),
expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 1px 2px -1px/),
])
expect(await getPropertyList('#f', 'box-shadow')).toEqual([
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'oklab(0 0 0 / 0.75) 0px 1px 2px 0px',
])
expect(await getPropertyList('#g', 'box-shadow')).toEqual([
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+ \/ 0.56\d+\) 0px 1px 2px 0px/),
])
await page.locator('#d').hover()
expect(await getPropertyValue('#d', 'box-shadow')).toEqual(
[
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgb(255, 0, 0) 0px 20px 25px -5px, rgb(255, 0, 0) 0px 8px 10px -6px',
].join(', '),
)
expect(await getPropertyList('#d', 'box-shadow')).toEqual([
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
expect(await getPropertyValue('#e', 'box-shadow')).toEqual(
[
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgb(255, 0, 0) 0px 1px 3px 0px, rgb(255, 0, 0) 0px 1px 2px -1px',
].join(', '),
)
//
expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 20px 25px -5px/),
expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 8px 10px -6px/),
])
expect(await getPropertyList('#e', 'box-shadow')).toEqual([
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
//
expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 1px 3px 0px/),
expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 1px 2px -1px/),
])
await page.locator('#e').hover()
expect(await getPropertyValue('#e', 'box-shadow')).toEqual(
[
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0.1) 0px 20px 25px -5px, rgba(0, 0, 0, 0.1) 0px 8px 10px -6px',
].join(', '),
)
expect(await getPropertyList('#e', 'box-shadow')).toEqual([
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
//
'rgba(0, 0, 0, 0.1) 0px 20px 25px -5px',
'rgba(0, 0, 0, 0.1) 0px 8px 10px -6px',
])
})
test('inset shadow colors', async ({ page }) => {
let { getPropertyValue } = await render(
let { getPropertyList } = await render(
page,
html`
<div id="a" class="inset-shadow-xs inset-shadow-red"></div>
@ -334,80 +362,85 @@ test('inset shadow colors', async ({ page }) => {
>
Hello world
</div>
<div id="f" class="inset-shadow-xs/75">Hello world</div>
<div id="g" class="inset-shadow-xs/75 inset-shadow-red/75">Hello world</div>
`,
)
expect(await getPropertyValue('#a', 'box-shadow')).toEqual(
[
'rgb(255, 0, 0) 0px 1px 1px 0px inset',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
].join(', '),
)
expect(await getPropertyValue('#b', 'box-shadow')).toEqual(
[
'rgb(255, 0, 0) 0px 2px 4px 0px inset',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
].join(', '),
)
expect(await getPropertyValue('#c', 'box-shadow')).toEqual(
[
'rgb(255, 0, 0) 0px 3px 6px 0px inset',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
].join(', '),
)
expect(await getPropertyList('#a', 'box-shadow')).toEqual([
expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 1px 1px 0px inset/),
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
])
expect(await getPropertyList('#b', 'box-shadow')).toEqual([
expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 2px 4px 0px inset/),
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
])
expect(await getPropertyList('#c', 'box-shadow')).toEqual([
expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 3px 6px 0px inset/),
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
])
expect(await getPropertyValue('#d', 'box-shadow')).toEqual(
[
'rgb(255, 0, 0) 0px 1px 1px 0px inset',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
].join(', '),
)
expect(await getPropertyList('#d', 'box-shadow')).toEqual([
expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 1px 1px 0px inset/),
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
])
expect(await getPropertyList('#f', 'box-shadow')).toEqual([
'oklab(0 0 0 / 0.75) 0px 1px 1px 0px inset',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
])
expect(await getPropertyList('#g', 'box-shadow')).toEqual([
expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+ \/ 0.56\d+\) 0px 1px 1px 0px inset/),
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
])
await page.locator('#d').hover()
expect(await getPropertyValue('#d', 'box-shadow')).toEqual(
[
'rgb(255, 0, 0) 0px 2px 4px 0px inset',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
].join(', '),
)
expect(await getPropertyList('#d', 'box-shadow')).toEqual([
expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 2px 4px 0px inset/),
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
])
expect(await getPropertyValue('#e', 'box-shadow')).toEqual(
[
'rgb(255, 0, 0) 0px 1px 1px 0px inset',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
].join(', '),
)
expect(await getPropertyList('#e', 'box-shadow')).toEqual([
expect.stringMatching(/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 1px 1px 0px inset/),
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
])
await page.locator('#e').hover()
expect(await getPropertyValue('#e', 'box-shadow')).toEqual(
[
'rgba(0, 0, 0, 0.05) 0px 2px 4px 0px inset',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
].join(', '),
)
expect(await getPropertyList('#e', 'box-shadow')).toEqual([
'rgba(0, 0, 0, 0.05) 0px 2px 4px 0px inset',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
'rgba(0, 0, 0, 0) 0px 0px 0px 0px',
])
})
test('text shadow colors', async ({ page }) => {
@ -424,24 +457,39 @@ test('text shadow colors', async ({ page }) => {
>
Hello world
</div>
<div id="f" class="text-shadow-xs/75">Hello world</div>
<div id="g" class="text-shadow-xs/75 text-shadow-red/75">Hello world</div>
`,
)
expect(await getPropertyValue('#a', 'text-shadow')).toEqual('rgb(255, 0, 0) 0px 1px 1px')
expect(await getPropertyValue('#b', 'text-shadow')).toEqual(
'rgb(255, 0, 0) 0px 1px 2px, rgb(255, 0, 0) 0px 3px 2px, rgb(255, 0, 0) 0px 4px 8px',
expect(await getPropertyValue('#a', 'text-shadow')).toMatch(
/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 1px 1px/,
)
expect(await getPropertyValue('#b', 'text-shadow')).toMatch(
/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 1px 2px, oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 3px 2px, oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 4px 8px/,
)
expect(await getPropertyValue('#c', 'text-shadow')).toMatch(
/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 2px 4px/,
)
expect(await getPropertyValue('#d', 'text-shadow')).toMatch(
/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 1px 1px/,
)
expect(await getPropertyValue('#c', 'text-shadow')).toEqual('rgb(255, 0, 0) 0px 2px 4px')
expect(await getPropertyValue('#d', 'text-shadow')).toEqual('rgb(255, 0, 0) 0px 1px 1px')
expect(await getPropertyValue('#f', 'text-shadow')).toEqual('oklab(0 0 0 / 0.75) 0px 1px 1px')
expect(await getPropertyValue('#g', 'text-shadow')).toMatch(
/oklab\(0.627\d+ 0.224\d+ 0.125\d+ \/ 0.56\d+\) 0px 1px 1px/,
)
await page.locator('#d').hover()
expect(await getPropertyValue('#d', 'text-shadow')).toEqual(
'rgb(255, 0, 0) 0px 1px 2px, rgb(255, 0, 0) 0px 3px 2px, rgb(255, 0, 0) 0px 4px 8px',
expect(await getPropertyValue('#d', 'text-shadow')).toMatch(
/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 1px 2px, oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 3px 2px, oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 4px 8px/,
)
expect(await getPropertyValue('#e', 'text-shadow')).toEqual('rgb(255, 0, 0) 0px 1px 1px')
expect(await getPropertyValue('#e', 'text-shadow')).toMatch(
/oklab\(0.627\d+ 0.224\d+ 0.125\d+\) 0px 1px 1px/,
)
await page.locator('#e').hover()
@ -803,6 +851,16 @@ async function render(page: Page, content: string, extraCss: string = '') {
property,
)
},
async getPropertyList(selector: string | [string, string], property: string) {
let value = await getPropertyValue(
page,
Array.isArray(selector) ? selector : [selector, undefined],
property,
)
return segment(value, ',').map((item) => item.trim())
},
}
}