Don't create bare spacing utilities with invalid multiples (#14962)

Closes #14960

When we moved to the `--spacing` multiples scale, we seemingly
overlooked a bail that caused us to use non-numerical values as a
spacing multiple. This caused the `-translate-x-full` and
`-translate-y-full` utilities to treat `full` as a valid multiple in our
spacing scale and created invalid CSS:

```css
.-translate-x-full {
  --tw-translate-x: calc(var(--spacing) * -x-full);
  --tw-translate-y: calc(var(--spacing) * -x-full);
  translate: var(--tw-translate-x) var(--tw-translate-y);
}
```


## Test plan

I reproduced the issue in our Vite playground and then created a failing
test case. It requires a `--spacing` `@theme` variable to be defined so
I've added this as a test case now in the unit tests. I also audited all
places that are using `calc()` and wrapping some numbers. In doing so I
found a few other broken cases:

- `-translate-x-full`
- `-translate-y-full`
- `-space-x-full`
- `-space-y-full`
- `-inset-full`

I validated that the fix indeed works and no longer creates broken CSS
definitions for these cases:

<kbd><img width="1405" alt="Screenshot 2024-11-11 at 19 33 51"
src="https://github.com/user-attachments/assets/99072112-9ed4-4456-bad8-5679679e7198"></kbd>

---------

Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This commit is contained in:
Philipp Spiess 2024-11-11 19:45:37 +01:00 committed by GitHub
parent 50d7355f7f
commit 98c279d1fb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 156 additions and 0 deletions

View File

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Don't reset horizontal padding on date/time pseudo-elements ([#14959](https://github.com/tailwindlabs/tailwindcss/pull/14959))
- Don't emit `calc()` with invalid values for bare values that aren't integers in spacing utilities ([#14962](https://github.com/tailwindlabs/tailwindcss/pull/14962))
## [4.0.0-alpha.32] - 2024-11-11

View File

@ -3861,6 +3861,82 @@ test('translate-x', async () => {
'-translate-x-[var(--value)]/foo',
]),
).toEqual('')
expect(
await compileCss(
css`
@theme {
--spacing: 0.25rem;
}
@tailwind utilities;
`,
['translate-x-full', '-translate-x-full', 'translate-x-px', '-translate-x-[var(--value)]'],
),
).toMatchInlineSnapshot(`
":root {
--spacing: .25rem;
}
.-translate-x-\\[var\\(--value\\)\\] {
--tw-translate-x: calc(var(--value) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.-translate-x-full {
--tw-translate-x: -100%;
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.translate-x-full {
--tw-translate-x: 100%;
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.translate-x-px {
--tw-translate-x: 1px;
translate: var(--tw-translate-x) var(--tw-translate-y);
}
@supports (-moz-orient: inline) {
@layer base {
*, :before, :after, ::backdrop {
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-translate-z: 0;
}
}
}
@property --tw-translate-x {
syntax: "<length> | <percentage>";
inherits: false;
initial-value: 0;
}
@property --tw-translate-y {
syntax: "<length> | <percentage>";
inherits: false;
initial-value: 0;
}
@property --tw-translate-z {
syntax: "<length>";
inherits: false;
initial-value: 0;
}"
`)
expect(
await run([
'perspective',
'-perspective',
'perspective-potato',
'perspective-123',
'perspective-normal/foo',
'perspective-dramatic/foo',
'perspective-none/foo',
'perspective-[456px]/foo',
]),
).toEqual('')
})
test('translate-y', async () => {
@ -3933,6 +4009,82 @@ test('translate-y', async () => {
'-translate-y-[var(--value)]/foo',
]),
).toEqual('')
expect(
await compileCss(
css`
@theme {
--spacing: 0.25rem;
}
@tailwind utilities;
`,
['translate-y-full', '-translate-y-full', 'translate-y-px', '-translate-y-[var(--value)]'],
),
).toMatchInlineSnapshot(`
":root {
--spacing: .25rem;
}
.-translate-y-\\[var\\(--value\\)\\] {
--tw-translate-y: calc(var(--value) * -1);
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.-translate-y-full {
--tw-translate-y: -100%;
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.translate-y-full {
--tw-translate-y: 100%;
translate: var(--tw-translate-x) var(--tw-translate-y);
}
.translate-y-px {
--tw-translate-y: 1px;
translate: var(--tw-translate-x) var(--tw-translate-y);
}
@supports (-moz-orient: inline) {
@layer base {
*, :before, :after, ::backdrop {
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-translate-z: 0;
}
}
}
@property --tw-translate-x {
syntax: "<length> | <percentage>";
inherits: false;
initial-value: 0;
}
@property --tw-translate-y {
syntax: "<length> | <percentage>";
inherits: false;
initial-value: 0;
}
@property --tw-translate-z {
syntax: "<length>";
inherits: false;
initial-value: 0;
}"
`)
expect(
await run([
'perspective',
'-perspective',
'perspective-potato',
'perspective-123',
'perspective-normal/foo',
'perspective-dramatic/foo',
'perspective-none/foo',
'perspective-[456px]/foo',
]),
).toEqual('')
})
test('translate-z', async () => {

View File

@ -387,6 +387,8 @@ export function createUtilities(theme: Theme) {
handleNegativeBareValue: ({ value }) => {
let multiplier = theme.resolve(null, ['--spacing'])
if (!multiplier) return null
if (!isValidSpacingMultiplier(value)) return null
return `calc(${multiplier} * -${value})`
},
handle,

View File

@ -2,6 +2,7 @@ export function App() {
return (
<div className="m-3 p-3 border">
<h1 className="text-blue-500">Hello World</h1>
<div className="-inset-x-full -inset-y-full -space-x-full -space-y-full -inset-full"></div>
</div>
)
}