diff --git a/CHANGELOG.md b/CHANGELOG.md index b3d7cfc0c..4c7a50040 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/packages/tailwindcss/src/__snapshots__/index.test.ts.snap b/packages/tailwindcss/src/__snapshots__/index.test.ts.snap index 049d0f33e..7a7cf1a2a 100644 --- a/packages/tailwindcss/src/__snapshots__/index.test.ts.snap +++ b/packages/tailwindcss/src/__snapshots__/index.test.ts.snap @@ -36,6 +36,12 @@ exports[`compiling CSS > \`@tailwind utilities\` is replaced by utilities using inherits: false } +@property --tw-shadow-alpha { + syntax: ""; + 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: ""; + inherits: false; + initial-value: 100%; +} + @property --tw-ring-color { syntax: "*"; inherits: false diff --git a/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap b/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap index 2c5091301..b988ed28c 100644 --- a/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap +++ b/packages/tailwindcss/src/__snapshots__/intellisense.test.ts.snap @@ -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", diff --git a/packages/tailwindcss/src/index.test.ts b/packages/tailwindcss/src/index.test.ts index 9a4279d54..6983bdb37 100644 --- a/packages/tailwindcss/src/index.test.ts +++ b/packages/tailwindcss/src/index.test.ts @@ -1499,6 +1499,12 @@ describe('Parsing theme values from CSS', () => { inherits: false } + @property --tw-shadow-alpha { + syntax: ""; + 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: ""; + inherits: false; + initial-value: 100%; + } + @property --tw-ring-color { syntax: "*"; inherits: false diff --git a/packages/tailwindcss/src/utilities.test.ts b/packages/tailwindcss/src/utilities.test.ts index 3ff8b1b69..1386dba42 100644 --- a/packages/tailwindcss/src/utilities.test.ts +++ b/packages/tailwindcss/src/utilities.test.ts @@ -198,6 +198,12 @@ test('inset', async () => { inherits: false } + @property --tw-shadow-alpha { + syntax: ""; + 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: ""; + 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: ""; + 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: ""; + 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: ""; + 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: ""; + 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: ""; + 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: ""; + 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: ""; + 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: ""; + 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: ""; + 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: ""; + 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: ""; + inherits: false; + initial-value: 100%; + } + @property --tw-ring-color { syntax: "*"; inherits: false diff --git a/packages/tailwindcss/src/utilities.ts b/packages/tailwindcss/src/utilities.ts index 10d4602b0..025179a12 100644 --- a/packages/tailwindcss/src/utilities.ts +++ b/packages/tailwindcss/src/utilities.ts @@ -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%', ''), + ]) } 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%', ''), property('--tw-inset-shadow', nullShadow), property('--tw-inset-shadow-color'), + property('--tw-inset-shadow-alpha', '100%', ''), 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, }, ]) diff --git a/packages/tailwindcss/src/utils/replace-shadow-colors.test.ts b/packages/tailwindcss/src/utils/replace-shadow-colors.test.ts index 3b1e90ba6..35aa89386 100644 --- a/packages/tailwindcss/src/utils/replace-shadow-colors.test.ts +++ b/packages/tailwindcss/src/utils/replace-shadow-colors.test.ts @@ -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) }, ) diff --git a/packages/tailwindcss/src/utils/replace-shadow-colors.ts b/packages/tailwindcss/src/utils/replace-shadow-colors.ts index e51f9c2a0..bf4239ee3 100644 --- a/packages/tailwindcss/src/utils/replace-shadow-colors.ts +++ b/packages/tailwindcss/src/utils/replace-shadow-colors.ts @@ -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. diff --git a/packages/tailwindcss/tests/ui.spec.ts b/packages/tailwindcss/tests/ui.spec.ts index 89dd467e3..7fbe0e58c 100644 --- a/packages/tailwindcss/tests/ui.spec.ts +++ b/packages/tailwindcss/tests/ui.spec.ts @@ -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`
>
`, ) - 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`
@@ -244,84 +245,111 @@ test('shadow colors', async ({ page }) => {
Hello world
+ +
Hello world
+
Hello world
`, ) - 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`
@@ -334,80 +362,85 @@ test('inset shadow colors', async ({ page }) => { > Hello world + +
Hello world
+
Hello world
`, ) - 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 + +
Hello world
+
Hello world
`, ) - 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()) + }, } }