tailwind_merge 0.16.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +84 -54
- data/lib/tailwind_merge/{class_utils.rb → class_group_utils.rb} +8 -29
- data/lib/tailwind_merge/config.rb +723 -418
- data/lib/tailwind_merge/parse_class_name.rb +89 -0
- data/lib/tailwind_merge/sort_modifiers.rb +31 -0
- data/lib/tailwind_merge/validators.rb +109 -45
- data/lib/tailwind_merge/version.rb +1 -1
- data/lib/tailwind_merge.rb +20 -18
- data/script/test +43 -0
- metadata +11 -9
- data/lib/tailwind_merge/modifier_utils.rb +0 -68
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 991c169d4f9e2e472db150202e1a6c523a7f4698ef04d576557d0bfd25ffef0e
|
4
|
+
data.tar.gz: f7f0e832b00f4dfbcb61e1460a53f80d2c438354a412c953f7ab3a59a78d9fdd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2d431e7225fb7720d7ef50f4998820f8be9c84c5944b686005f78ef90a27895552c98ae39f4e9093c8bb90f1577cac4dc3a4d9483badb6b8b04088df7b00a611
|
7
|
+
data.tar.gz: 996fe4b3bdf0bb8a8a4ddf2b3f9cf6c950879c38bac9ec39bfc4e6b65913f2f48764fc6923e625e0aefcdfcab985556e805dc0ff41f3381766be5c3d9a8d59df
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
# [v1.0.0] - 25-02-2025
|
2
|
+
## What's Changed
|
3
|
+
* Add matrix to test CI by @w-masahiro-ct in https://github.com/gjtorikian/tailwind_merge/pull/47
|
4
|
+
* [breaking] Support Tailwind v4 by @gjtorikian in https://github.com/gjtorikian/tailwind_merge/pull/52
|
5
|
+
|
6
|
+
|
7
|
+
**Full Changelog**: https://github.com/gjtorikian/tailwind_merge/compare/v0.16.0...v1.0.0
|
1
8
|
# [v0.16.0] - 25-01-2025
|
2
9
|
## What's Changed
|
3
10
|
* Fix and improve implementation by @w-masahiro-ct in https://github.com/gjtorikian/tailwind_merge/pull/50
|
data/README.md
CHANGED
@@ -2,7 +2,8 @@
|
|
2
2
|
|
3
3
|
Utility function to efficiently merge [Tailwind CSS](https://tailwindcss.com/) classes without style conflicts. Essentially, a Ruby port of [tailwind-merge](https://github.com/dcastil/tailwind-merge).
|
4
4
|
|
5
|
-
|
5
|
+
- Version 1.0 and above supports Tailwind v4.0+.
|
6
|
+
- Versions below that support from v3.0 up to v3.4.
|
6
7
|
|
7
8
|
## Installation
|
8
9
|
|
@@ -14,6 +15,8 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
14
15
|
|
15
16
|
$ gem install tailwind_merge
|
16
17
|
|
18
|
+
## Usage
|
19
|
+
|
17
20
|
To use it, pass in a single string:
|
18
21
|
|
19
22
|
```ruby
|
@@ -96,7 +99,7 @@ The order of standard modifiers does not matter for tailwind-merge.
|
|
96
99
|
### Supports arbitrary values
|
97
100
|
|
98
101
|
```ruby
|
99
|
-
@merger.merge("bg-black bg-[color:var(--mystery-var)]") # → "bg-[color:var(--mystery-var)]"
|
102
|
+
@merger.merge("bg-black bg-(--my-color) bg-[color:var(--mystery-var)]") # → "bg-[color:var(--mystery-var)]"
|
100
103
|
@merger.merge("grid-cols-[1fr,auto] grid-cols-2") # → "grid-cols-2"
|
101
104
|
```
|
102
105
|
|
@@ -136,8 +139,8 @@ The order of standard modifiers does not matter for tailwind-merge.
|
|
136
139
|
### Supports important modifier
|
137
140
|
|
138
141
|
```ruby
|
139
|
-
@merger.merge("
|
140
|
-
@merger.merge("
|
142
|
+
@merger.merge("p-3! p-4! p-5") # → "p-4! p-5"
|
143
|
+
@merger.merge("right-2! -inset-x-1!") # → "-inset-x-1!"
|
141
144
|
```
|
142
145
|
|
143
146
|
## Supports postfix modifiers
|
@@ -183,18 +186,16 @@ The `tailwind_merge` config is different from the Tailwind config because it's e
|
|
183
186
|
|
184
187
|
## Configuration
|
185
188
|
|
186
|
-
The `tailwind_merge` config is an object with several keys
|
189
|
+
The `tailwind_merge` config is an object with several keys. All of these values are optional.
|
187
190
|
|
188
191
|
```ruby
|
189
192
|
tailwind_merge_config = {
|
190
|
-
#
|
193
|
+
# Define how many values should be stored in cache.
|
191
194
|
cache_size: 500,
|
192
|
-
#
|
195
|
+
# Enable or disable caching nil values.
|
193
196
|
ignore_empty_cache: true,
|
194
|
-
#
|
195
|
-
|
196
|
-
# ↓ *Optional* prefix from Tailwind config
|
197
|
-
prefix: "tw-",
|
197
|
+
# Prefix from your Tailwind config
|
198
|
+
prefix: "tw",
|
198
199
|
theme: {
|
199
200
|
# Theme scales are defined here
|
200
201
|
# This is not the theme object from your Tailwind config
|
@@ -204,7 +205,10 @@ tailwind_merge_config = {
|
|
204
205
|
},
|
205
206
|
conflicting_class_groups: {
|
206
207
|
# Conflicts between class groups are defined here
|
207
|
-
}
|
208
|
+
},
|
209
|
+
# Modifiers whose order among multiple modifiers should be preserved because their
|
210
|
+
# order changes which element gets targeted. Overrides default value.
|
211
|
+
order_sensitive_modifiers: [],
|
208
212
|
}
|
209
213
|
```
|
210
214
|
|
@@ -278,65 +282,91 @@ If a class group _creates_ a conflict, it means that if it appears in a class li
|
|
278
282
|
|
279
283
|
When we think of our example, the `px` class group creates a conflict which is received by the class groups `pr` and `pl`. This way `px-3` removes a preceding `pr-4`, but not the other way around.
|
280
284
|
|
285
|
+
### Postfix modifiers conflicting with class groups
|
286
|
+
|
287
|
+
Tailwind CSS allows postfix modifiers for some classes. E.g. you can set font-size and line-height together with `text-lg/7` with `/7` being the postfix modifier. This means that any line-height classes preceding a font-size class with a modifier should be removed.
|
288
|
+
For this tailwind-merge has the `conflicting_class_groups` object in its config with the same shape as `conflicting_class_groups` explained in the [section above](#conflicting-class-groups). This time the key is the ID of a class group whose modifier _creates_ a conflict and the value is an array of IDs of class groups which _receive_ the conflict.
|
289
|
+
|
290
|
+
```ruby
|
291
|
+
conflicting_class_groups = {
|
292
|
+
"font-size": ["leading"],
|
293
|
+
};
|
294
|
+
```
|
295
|
+
|
296
|
+
### Order-sensitive modifiers
|
297
|
+
|
298
|
+
In Tailwind CSS, not all modifiers behave the same when you stack them.
|
299
|
+
|
300
|
+
In most cases the order of modifiers doesn't matter. E.g. `hover:focus:bg-red-500` and `focus:hover:bg-red-500` behave the same and in the context of tailwind-merge, you'd want them both to override each other. tailwind-merge sorts the modifiers internally to be able to override classes with the same modifiers, even if they are in a different order.
|
301
|
+
|
302
|
+
However, there are some modifiers where the order matters, e.g. the direct children modifier `*`. The class `*:hover:text-red-500` modifies the text color of a child if that particular child is hovered, but the class `hover:*:text-red-500` modifies the text color of all direct children if the parent is hovered. In this case, you would want tailwind-merge to preserve both classes although they have the same modifiers, just in a different order.
|
303
|
+
|
304
|
+
To know which modifiers are order-sensitive, tailwind-merge has the `orderSensitiveModifiers` property in its config. `twMerge` is pre-configured with all the order-sensitive modifiers that Tailwind CSS has by default. You'll only need to configure this property if you add your own order-sensitive modifiers or change the meaning of the default order-sensitive modifiers.
|
305
|
+
|
281
306
|
### Theme
|
282
307
|
|
283
|
-
In the Tailwind config you can modify theme
|
284
|
-
|
285
|
-
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
- `
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
- `
|
299
|
-
- `
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
- `
|
309
|
-
- `translate`
|
310
|
-
|
311
|
-
If you modified one of these theme scales in your Tailwind config, you can add all your keys right here and tailwind-merge will take care of the rest. For example, to add custom spaces and margin, you would provide the following `theme`:
|
308
|
+
In the Tailwind config you can modify your theme variable namespace to add classes with custom values. tailwind-merge follows the same naming scheme as Tailwind CSS for its theme scales:
|
309
|
+
|
310
|
+
| Tailwind CSS namespace | tailwind-merge theme key |
|
311
|
+
| ---------------------- | ------------------------ |
|
312
|
+
| `--color-*` | `color` |
|
313
|
+
| `--font-*` | `font` |
|
314
|
+
| `--text-*` | `text` |
|
315
|
+
| `--font-weight-*` | `font-weight` |
|
316
|
+
| `--tracking-*` | `tracking` |
|
317
|
+
| `--leading-*` | `leading` |
|
318
|
+
| `--breakpoint-*` | `breakpoint` |
|
319
|
+
| `--container-*` | `container` |
|
320
|
+
| `--spacing-*` | `spacing` |
|
321
|
+
| `--radius-*` | `radius` |
|
322
|
+
| `--shadow-*` | `shadow` |
|
323
|
+
| `--inset-shadow-*` | `inset-shadow` |
|
324
|
+
| `--drop-shadow-*` | `drop-shadow` |
|
325
|
+
| `--blur-*` | `blur` |
|
326
|
+
| `--perspective-*` | `perspective` |
|
327
|
+
| `--aspect-*` | `aspect` |
|
328
|
+
| `--ease-*` | `ease` |
|
329
|
+
| `--animate-*` | `animate` |
|
330
|
+
|
331
|
+
If you modified one of the theme namespaces in your Tailwind config, you need to add the variable names to the `theme` object in tailwind-merge as well so that tailwind-merge knows about them.
|
332
|
+
|
333
|
+
E.g. let's say you added the variable `--text-huge-af: 100px` to your Tailwind config which enables classes like `text-huge-af`. To make sure that tailwind-merge merges these classes correctly, you need to configure tailwind-merge like this:
|
312
334
|
|
313
335
|
```ruby
|
314
336
|
merger = TailwindMerge::Merger.new(config: {
|
315
|
-
|
316
|
-
|
317
|
-
|
337
|
+
theme: {
|
338
|
+
# ↓ `text` is the key of the namespace `--text-*`
|
339
|
+
# ↓ `huge-af` is the variable name in the namespace
|
340
|
+
text: ["huge-af"],
|
318
341
|
}
|
319
342
|
})
|
320
343
|
```
|
321
344
|
|
322
|
-
If you modified other theme scales, you need to figure out the class group to modify in the [default config](#getdefaultconfig).
|
323
|
-
|
324
345
|
### Validators
|
325
346
|
|
326
347
|
Here's a brief summary for each validator:
|
327
348
|
|
328
|
-
- `
|
349
|
+
- `IS_ANY` always returns true. Be careful with this validator as it might match unwanted classes. I use it primarily to match colors or when I'm certain there are no other class groups in a namespace.
|
350
|
+
- `IS_ANY_NON_ARBITRARY` checks if the class part is not an arbitrary value or arbitrary variable.
|
351
|
+
- `IS_ARBITRARY_IMAGE` checks whether class part is an arbitrary value which is an iamge, e.g. by starting with `image:`, `url:`, `linear-gradient(` or `url(` (`[url('/path-to-image.png')]`, `image:var(--maybe-an-image-at-runtime)]`) which is necessary for background-image classNames.
|
329
352
|
- `IS_ARBITRARY_LENGTH` checks for arbitrary length values (`[3%]`, `[4px]`, `[length:var(--my-var)]`).
|
353
|
+
- `IS_ARBITRARY_NUMBER` checks whether class part is an arbitrary value which starts with `number:` or is a number (`[number:var(--value)]`, `[450]`) which is necessary for font-weight and stroke-width classNames.
|
354
|
+
- `IS_ARBITRARY_POSITION` checks whether class part is an arbitrary value which starts with `position:` (`[position:200px_100px]`) which is necessary for background-position classNames.
|
355
|
+
- `IS_ARBITRARY_SHADOW` checks whether class part is an arbitrary value which starts with the same pattern as a shadow value (`[0_35px_60px_-15px_rgba(0,0,0,0.3)]`), namely with two lengths separated by a underscore, optionally prepended by `inset`.
|
356
|
+
- `IS_ARBITRARY_SIZE` checks whether class part is an arbitrary value which starts with `size:` (`[size:200px_100px]`) which is necessary for background-size classNames.
|
357
|
+
- `IS_ARBITRARY_VALUE` checks whether the class part is enclosed in brackets (`[something]`)
|
358
|
+
- `IS_ARBITRARY_VARIABLE` checks whether the class part is an arbitrary variable (`(--my-var)`)
|
359
|
+
- `IS_ARBITRARY_VARIABLE_FAMILYNAME` checks whether class part is an arbitrary variable with the `family-name` label (`(family-name:--my-font)`)
|
360
|
+
- `IS_ARBITRARY_VARIABLE_IMAGE` checks whether class part is an arbitrary variable with the `image` or `url` label (`(image:--my-image)`)
|
361
|
+
- `IS_ARBITRARY_VARIABLE_LENGTH` checks whether class part is an arbitrary variable with the `length` label (`(length:--my-length)`)
|
362
|
+
- `IS_ARBITRARY_VARIABLE_POSITION` checks whether class part is an arbitrary variable with the `position` label (`(position:--my-position)`)
|
363
|
+
- `IS_ARBITRARY_VARIABLE_SHADOW` checks whether class part is an arbitrary variable with the `shadow` label or not label at all (`(shadow:--my-shadow)`, `(--my-shadow)`)
|
364
|
+
- `IS_ARBITRARY_VARIABLE_SIZE` checks whether class part is an arbitrary variable with the `size`, `length` or `percentage` label (`(size:--my-size)`)
|
365
|
+
- `IS_FRACTION` checks whether class part is a fraction of two numbers (`1/2`, `127/256`)
|
330
366
|
- `IS_INTEGER` checks for integer values (`3`).
|
367
|
+
- `IS_NUMBER` checks for numbers (`3`, `1.5`)
|
331
368
|
- `IS_PERCENT` checks for percent values (`12.5%`) which is used for color stop positions.
|
332
|
-
- `IS_ARBITRARY_VALUE` checks whether the class part is enclosed in brackets (`[something]`)
|
333
369
|
- `IS_TSHIRT_SIZE`checks whether class part is a T-shirt size (`sm`, `xl`), optionally with a preceding number (`2xl`).
|
334
|
-
- `IS_ARBITRARY_SIZE` checks whether class part is an arbitrary value which starts with `size:` (`[size:200px_100px]`) which is necessary for background-size classNames.
|
335
|
-
- `IS_ARBITRARY_POSITION` checks whether class part is an arbitrary value which starts with `position:` (`[position:200px_100px]`) which is necessary for background-position classNames.
|
336
|
-
- `IS_ARBITRARY_IMAGE` checks whether class part is an arbitrary value which is an iamge, e.g. by starting with `image:`, `url:`, `linear-gradient(` or `url(` (`[url('/path-to-image.png')]`, `image:var(--maybe-an-image-at-runtime)]`) which is necessary for background-image classNames.
|
337
|
-
- `IS_ARBITRARY_NUMBER` checks whether class part is an arbitrary value which starts with `number:` or is a number (`[number:var(--value)]`, `[450]`) which is necessary for font-weight classNames.
|
338
|
-
- `IS_ARBITRARY_SHADOW` checks whether class part is an arbitrary value which starts with the same pattern as a shadow value (`[0_35px_60px_-15px_rgba(0,0,0,0.3)]`), namely with two lengths separated by a underscore, optionally prepended by `inset`.
|
339
|
-
- `IS_ANY` always returns true. Be careful with this validator as it might match unwanted classes. I use it primarily to match colors or when it's certain there are no other class groups in a namespace.
|
340
370
|
|
341
371
|
## Performance
|
342
372
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module TailwindMerge
|
4
|
-
class
|
4
|
+
class ClassGroupUtils
|
5
5
|
attr_reader :class_map
|
6
6
|
|
7
7
|
CLASS_PART_SEPARATOR = "-"
|
@@ -58,50 +58,28 @@ module TailwindMerge
|
|
58
58
|
end
|
59
59
|
|
60
60
|
private def create_class_map(config)
|
61
|
-
|
61
|
+
theme = config[:theme]
|
62
|
+
class_groups = config[:class_groups]
|
62
63
|
class_map = {
|
63
64
|
next_part: {},
|
64
65
|
validators: [],
|
65
66
|
}
|
66
67
|
|
67
|
-
|
68
|
-
|
69
|
-
prefix,
|
70
|
-
)
|
71
|
-
|
72
|
-
prefixed_class_group_entries.each do |class_group_id, class_group|
|
73
|
-
process_classes_recursively(class_group, class_map, class_group_id)
|
68
|
+
class_groups.each do |(class_group_id, class_group)|
|
69
|
+
process_classes_recursively(class_group, class_map, class_group_id, theme)
|
74
70
|
end
|
75
71
|
|
76
72
|
class_map
|
77
73
|
end
|
78
74
|
|
79
|
-
private def
|
80
|
-
return class_group_entries if prefix.nil?
|
81
|
-
|
82
|
-
class_group_entries.map do |class_group_id, class_group|
|
83
|
-
prefixed_class_group = class_group.map do |class_definition|
|
84
|
-
if class_definition.is_a?(String)
|
85
|
-
"#{prefix}#{class_definition}"
|
86
|
-
elsif class_definition.is_a?(Hash)
|
87
|
-
class_definition.transform_keys { |key| "#{prefix}#{key}" }
|
88
|
-
else
|
89
|
-
class_definition
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
[class_group_id, prefixed_class_group]
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
private def process_classes_recursively(class_group, class_part_object, class_group_id)
|
75
|
+
private def process_classes_recursively(class_group, class_part_object, class_group_id, theme)
|
98
76
|
class_group.each do |class_definition|
|
99
77
|
if class_definition.is_a?(String)
|
100
78
|
class_part_object_to_edit = class_definition.empty? ? class_part_object : get_class_part(class_part_object, class_definition)
|
101
79
|
class_part_object_to_edit[:class_group_id] = class_group_id
|
102
80
|
elsif class_definition.is_a?(Proc)
|
103
81
|
if from_theme?(class_definition)
|
104
|
-
process_classes_recursively(class_definition.call(@config), class_part_object, class_group_id)
|
82
|
+
process_classes_recursively(class_definition.call(@config), class_part_object, class_group_id, theme)
|
105
83
|
else
|
106
84
|
class_part_object[:validators] << {
|
107
85
|
validator: class_definition,
|
@@ -114,6 +92,7 @@ module TailwindMerge
|
|
114
92
|
nested_class_group,
|
115
93
|
get_class_part(class_part_object, key),
|
116
94
|
class_group_id,
|
95
|
+
theme,
|
117
96
|
)
|
118
97
|
end
|
119
98
|
end
|