6 Commits

Author SHA1 Message Date
Robin Malfait
113142a0e4
Use amount of properties when sorting (#16715)
Right now we sort the nodes based on a pre-defined sort order based on
the properties that are being used. The property sort order is defined
in a list we maintain.

We also have to make sure that the property count is taken into account
such that if all the "sorts" are the same, that we fallback to the
property count. Most amount of properties should be first such that we
can override it with more specific utilities that have fewer properties.

However, if a property doesn't exist, then it wouldn't be included in a
list of properties therefore the total count was off.

This PR fixes that by counting all the used properties. If a property
already exists it is counted twice. E.g.:
```css
.foo {
  color: red;

  &:hover {
    color: blue;
  }
}
```

In this case, we have 2 properties, not 1 even though it's the same
`color` property.

## Test plan:

1. Updated the tests that are now sorted correctly
2. Added an integration test to make sure that `prose-invert` is defined
after the `prose-stone` classes when using the `@tailwindcss/typography`
plugin where this problem originated from.

Note how in this play (https://play.tailwindcss.com/wt3LYDaljN) the
`prose-invert` comes _before_ the `prose-stone` which means that you
can't apply the `prose-invert` classes to invert `prose-stone`.
2025-02-21 15:02:07 +01:00
Philipp Spiess
ab9e2b716b
Support complex addUtilities() configs (#15029)
This PR adds support for complex `addUtilities()` configuration objects
that use child combinators and other features.

For example, in v3 it was possible to add a utility that changes the
behavior of all children of the utility class node by doing something
like this:

```ts
addUtilities({
  '.red-children > *': {
    color: 'red',
  },
});
```

This is a pattern that was used by first-party plugins like
`@tailwindcss/aspect-ratio` but that we never made working in v4, since
it requires parsing the selector and properly extracting all utility
candidates.

While working on the codemod that can transform `@layer utilities`
scoped declarations like the above, we found out a pretty neat
heuristics on how to migrate these cases. We're basically finding all
class selectors and replace them with `&`. Then we create a nested CSS
structure like this:

```css
.red-children {
  & > * {
    color: red;
  }
}
```

Due to first party support for nesting, this works as expected in v4.

## Test Plan

We added unit tests to ensure the rewriting works in some edge cases.
Furthermore we added an integration test running the
`@tailwindcss/aspect-ratio` plugin. We've also installed the tarballs in
the Remix example from the
[playgrounds](https://github.com/philipp-spiess/tailwindcss-playgrounds)
and ensure we can use the `@tailwindcss/aspect-ratio` plugin just like
we could in v3:
 
<img width="2560" alt="Screenshot 2024-11-18 at 13 44 52"
src="https://github.com/user-attachments/assets/31889131-fad0-4c37-b574-cfac2b99f786">

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-11-19 15:52:06 +01:00
thecrypticace
c0f29225e4 Always emit keyframes registered in addUtilities (#14747)
Fixes #14732

cc @philipp-spiess this look like an okay fix?
2024-10-22 16:34:38 +00:00
Jordan Pittman
c65f20ace0
Support plugin options in CSS (#14264)
Builds on #14239 — that PR needs to be merged first.

This PR allows plugins defined with `plugin.withOptions` to receive
options in CSS when using `@plugin` as long as the options are simple
key/value pairs.

For example, the following is now valid and will include the forms
plugin with only the base styles enabled:

```css
@plugin "@tailwindcss/forms" {
  strategy: base;
}
```

We handle `null`, `true`, `false`, and numeric values as expected and
will convert them to their JavaScript equivalents. Comma separated
values are turned into arrays. All other values are converted to
strings.

For example, in the following plugin definition, the options that are
passed to the plugin will be the correct types:
- `debug` will be the boolean value `true`
- `threshold` will be the number `0.5`
- `message` will be the string `"Hello world"`
- `features` will be the array `["base", "responsive"]`

```css
@plugin "my-plugin" {
  debug: false;
  threshold: 0.5;
  message: Hello world;
  features: base, responsive;
}
```

If you need to pass a number or boolean value as a string, you can do so
by wrapping the value in quotes:

```css
@plugin "my-plugin" {
  debug: "false";
  threshold: "0.5";
  message: "Hello world";
}
```

When duplicate options are encountered the last value wins:

```css
@plugin "my-plugin" {
  message: Hello world;
  message: Hello plugin; /* this will be the value of `message` */
}
```

It's important to note that this feature is **only available for plugins
defined with `plugin.withOptions`**. If you try to pass options to a
plugin that doesn't support them, you'll get an error message when
building:

```css
@plugin "my-plugin" {
  debug: false;
  threshold: 0.5;
}

/* Error: The plugin "my-plugin" does not accept options */
```

Additionally, if you try to pass in more complex values like objects or
selectors you'll get an error message:

```css
@plugin "my-plugin" {
  color: { red: 100; green: 200; blue: 300 };
}

/* Error: Objects are not supported in `@plugin` options. */
```

```css
@plugin "my-plugin" {
  .some-selector > * {
    primary: "blue";
    secondary: "green";
  }
}

/* Error: `@plugin` can only contain declarations. */
```

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-09-02 12:49:09 -04:00
Jordan Pittman
cc228fbfc3
Add support for matching multiple utility definitions for one candidate (#14231)
Currently if a plugin adds a utility called `duration` it will take
precedence over the built-in utilities — or any utilities with the same
name in previously included plugins. However, in v3, we emitted matches
from _all_ plugins where possible.

Take this plugin for example which adds utilities for
`animation-duration` via the `duration-*` class:

```ts
import plugin from 'tailwindcss/plugin'

export default plugin(
  function ({ matchUtilities, theme }) {
    matchUtilities(
      { duration: (value) => ({ animationDuration: value }) },
      { values: theme("animationDuration") },
    )
  },
  {
    theme: {
      extend: {
        animationDuration: ({ theme }) => ({
          ...theme("transitionDuration"),
        }),
      }
    },
  }
)
```

Before this PR this plugin's `duration` utility would override the
built-in `duration` utility so you'd get this for a class like
`duration-3500`:
```css
.duration-3000 {
  animation-duration: 3500ms;
}
```

Now, after this PR, we'll emit rules for `transition-duration`
(Tailwind's built-in `duration-*` utility) and `animation-duration`
(from the above plugin) and you'll get this instead:
```css
.duration-3000 {
  transition-duration: 3500ms;
}

.duration-3000 {
  animation-duration: 3500ms;
}
```

These are output as separate rules to ensure that they can all be sorted
appropriately against other utilities.

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-08-22 16:22:12 +02:00
Jordan Pittman
30bbe51a38
Improve compatibility with @tailwindcss/typography and @tailwindcss/forms (#14221)
This PR enables compatibility for the `@tailwindcss/typography` and
`@tailwindcss/forms` plugins. This required the addition of new Plugin
APIs and new package exports.

## New Plugin APIs and compatibility improvements

We added support for `addComponents`, `matchComponents`, and `prefix`.
The component APIs are an alias for the utilities APIs because the
sorting in V4 is different and emitting components in a custom `@layer`
is not necessary. Since `prefix` is not supported in V4, the `prefix()`
API is currently an identity function.

```js
 addComponents({
  '.btn': {
    padding: '.5rem 1rem',
    borderRadius: '.25rem',
    fontWeight: '600',
  },
  '.btn-blue': {
    backgroundColor: '#3490dc',
    color: '#fff',
    '&:hover': {
      backgroundColor: '#2779bd',
    },
  },
  '.btn-red': {
    backgroundColor: '#e3342f',
    color: '#fff',
    '&:hover': {
      backgroundColor: '#cc1f1a',
    },
  },
})
```

The behavioral changes effect the `addUtilities` and `matchUtilities`
functions, we now:

- Allow arrays of CSS property objects to be emitted:
  ```js
  addUtilities({
    '.text-trim': [
      {'text-box-trim': 'both'},
      {'text-box-edge': 'cap alphabetic'},
    ],
  })
  ```
- Allow arrays of utilities
  ```js
  addUtilities([
    {
      '.text-trim':{
        'text-box-trim': 'both',
        'text-box-edge': 'cap alphabetic',
      },
    }
  ])
  ```
- Allow more complicated selector names
  ```js
  addUtilities({
    '.form-input, .form-select, .form-radio': {
      /* styles here */
    },
    '.form-input::placeholder': {
      /* styles here */
    },
    '.form-checkbox:indeterminate:checked': {
      /* styles here */
    }
  })
  ```

## New `tailwindcss/color` and `tailwindcss/defaultTheme` export

To be compatible to v3, we're adding two new exports to the tailwindcss
package. These match the default theme values as defined in v3:

```ts
import colors from 'tailwindcss/colors'

console.log(colors.red[600])
```

```ts
import theme from 'tailwindcss/defaultTheme'

console.log(theme.spacing[4])
```

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-08-22 08:06:21 -04:00