tailwind_merge 0.14.0 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -0
- data/README.md +53 -55
- data/lib/tailwind_merge/class_utils.rb +39 -57
- data/lib/tailwind_merge/config.rb +34 -56
- data/lib/tailwind_merge/modifier_utils.rb +21 -25
- data/lib/tailwind_merge/validators.rb +3 -5
- data/lib/tailwind_merge/version.rb +1 -1
- data/lib/tailwind_merge.rb +31 -37
- data/tailwind_merge.gemspec +11 -8
- metadata +12 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3334600a104bc6b8efbe365c3be28e32944468f9cb5b1255fdefd4d106dd9540
|
4
|
+
data.tar.gz: 2f8c9a9b989c3e808767516bff6b040ed0c3a092a5fc427aaacaae4904523bcc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7f8ad1a5e8dfc2386b81cf5f528e2b1b5ded8b9644dd942e777e3b356b0807d74397400c090b52aec68d54e93bc0bd8d672f9872795dd300b7e2b06b543701af
|
7
|
+
data.tar.gz: 33cc99d39060f7f2e6827d48f99bce9989df5771f0174eea1994133a25e05115855de5e4f138b763f7197801595c3ab8e13802291675786f782f7246429079dd
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,22 @@
|
|
1
|
+
# [v0.16.0] - 25-01-2025
|
2
|
+
## What's Changed
|
3
|
+
* Fix and improve implementation by @w-masahiro-ct in https://github.com/gjtorikian/tailwind_merge/pull/50
|
4
|
+
|
5
|
+
|
6
|
+
**Full Changelog**: https://github.com/gjtorikian/tailwind_merge/compare/v0.15.0...v0.16.0
|
7
|
+
# [v0.15.0] - 23-01-2025
|
8
|
+
## What's Changed
|
9
|
+
* Improve gemspec by @w-masahiro-ct in https://github.com/gjtorikian/tailwind_merge/pull/41
|
10
|
+
* Refactoring README.md by @w-masahiro-ct in https://github.com/gjtorikian/tailwind_merge/pull/42
|
11
|
+
* Fix homepage uri by @w-masahiro-ct in https://github.com/gjtorikian/tailwind_merge/pull/45
|
12
|
+
* Refactoring constants by @w-masahiro-ct in https://github.com/gjtorikian/tailwind_merge/pull/43
|
13
|
+
* Fix changelog uri by @w-masahiro-ct in https://github.com/gjtorikian/tailwind_merge/pull/46
|
14
|
+
* Uninstall lru_cache and install sin_lru_redux by @w-masahiro-ct in https://github.com/gjtorikian/tailwind_merge/pull/48
|
15
|
+
- Adds `ignore_empty_cache` option for saving memory
|
16
|
+
## New Contributors
|
17
|
+
* @w-masahiro-ct made their first contribution in https://github.com/gjtorikian/tailwind_merge/pull/41
|
18
|
+
|
19
|
+
**Full Changelog**: https://github.com/gjtorikian/tailwind_merge/compare/v0.14.0...v0.15.0
|
1
20
|
# [v0.14.0] - 19-12-2024
|
2
21
|
## What's Changed
|
3
22
|
* Prevent cache tampering by freezing cached value by @david-uhlig in https://github.com/gjtorikian/tailwind_merge/pull/39
|
data/README.md
CHANGED
@@ -20,7 +20,7 @@ To use it, pass in a single string:
|
|
20
20
|
require "tailwind_merge"
|
21
21
|
|
22
22
|
TailwindMerge::Merger.new.merge("px-2 py-1 bg-red hover:bg-dark-red p-3 bg-[#B91C1C]")
|
23
|
-
# →
|
23
|
+
# → "hover:bg-dark-red p-3 bg-[#B91C1C]"
|
24
24
|
```
|
25
25
|
|
26
26
|
Or, an array of strings:
|
@@ -29,7 +29,7 @@ Or, an array of strings:
|
|
29
29
|
require "tailwind_merge"
|
30
30
|
|
31
31
|
TailwindMerge::Merger.new.merge(["px-2 py-1", "bg-red hover:bg-dark-red", "p-3 bg-[#B91C1C]"])
|
32
|
-
# →
|
32
|
+
# → "hover:bg-dark-red p-3 bg-[#B91C1C]"
|
33
33
|
```
|
34
34
|
|
35
35
|
## What's it for?
|
@@ -50,10 +50,8 @@ When the `ConfirmEmailComponent` is rendered, an input with the className `borde
|
|
50
50
|
This is where `tailwind_merge` comes in:
|
51
51
|
|
52
52
|
```ruby
|
53
|
-
|
54
53
|
@merger = TailwindMerge::Merger.new
|
55
|
-
@merger.merge("border rounded px-2 py-1 p-5")
|
56
|
-
# → "border rounded p-5"
|
54
|
+
@merger.merge("border rounded px-2 py-1 p-5") # → "border rounded p-5"
|
57
55
|
```
|
58
56
|
|
59
57
|
tailwind-merge overrides conflicting classes and keeps everything else untouched. In the case of the implementation of `ConfirmEmailComponent`, the input now only renders the classes `border rounded p-5`.
|
@@ -67,30 +65,30 @@ tailwind-merge overrides conflicting classes and keeps everything else untouched
|
|
67
65
|
### Last conflicting class wins
|
68
66
|
|
69
67
|
```ruby
|
70
|
-
@merger.merge(
|
68
|
+
@merger.merge("p-5 p-2 p-4") # → "p-4"
|
71
69
|
```
|
72
70
|
|
73
71
|
### Supports refinements
|
74
72
|
|
75
73
|
```ruby
|
76
|
-
@merger.merge(
|
77
|
-
@merger.merge(
|
74
|
+
@merger.merge("p-3 px-5") # → "p-3 px-5"
|
75
|
+
@merger.merge("inset-x-4 right-4") # → "inset-x-4 right-4"
|
78
76
|
```
|
79
77
|
|
80
78
|
### Resolves non-trivial conflicts
|
81
79
|
|
82
80
|
```ruby
|
83
|
-
@merger.merge(
|
84
|
-
@merger.merge(
|
85
|
-
@merger.merge(
|
81
|
+
@merger.merge("inset-x-px -inset-1") # → "-inset-1"
|
82
|
+
@merger.merge("bottom-auto inset-y-6") # → "inset-y-6"
|
83
|
+
@merger.merge("inline block") # → "block"
|
86
84
|
```
|
87
85
|
|
88
86
|
### Supports modifiers and stacked modifiers
|
89
87
|
|
90
88
|
```ruby
|
91
|
-
@merger.merge(
|
92
|
-
@merger.merge(
|
93
|
-
@merger.merge(
|
89
|
+
@merger.merge("p-2 hover:p-4") # → "p-2 hover:p-4"
|
90
|
+
@merger.merge("hover:p-2 hover:p-4") # → "hover:p-4"
|
91
|
+
@merger.merge("hover:focus:p-2 focus:hover:p-4") # → "focus:hover:p-4"
|
94
92
|
```
|
95
93
|
|
96
94
|
The order of standard modifiers does not matter for tailwind-merge.
|
@@ -98,8 +96,8 @@ The order of standard modifiers does not matter for tailwind-merge.
|
|
98
96
|
### Supports arbitrary values
|
99
97
|
|
100
98
|
```ruby
|
101
|
-
@merger.merge(
|
102
|
-
@merger.merge(
|
99
|
+
@merger.merge("bg-black bg-[color:var(--mystery-var)]") # → "bg-[color:var(--mystery-var)]"
|
100
|
+
@merger.merge("grid-cols-[1fr,auto] grid-cols-2") # → "grid-cols-2"
|
103
101
|
```
|
104
102
|
|
105
103
|
> **Warning**
|
@@ -112,11 +110,11 @@ The order of standard modifiers does not matter for tailwind-merge.
|
|
112
110
|
### Supports arbitrary properties
|
113
111
|
|
114
112
|
```ruby
|
115
|
-
@merger.merge(
|
116
|
-
@merger.merge(
|
113
|
+
@merger.merge("[mask-type:luminance] [mask-type:alpha]") # → "[mask-type:alpha]"
|
114
|
+
@merger.merge("[--scroll-offset:56px] lg:[--scroll-offset:44px]") # → "[--scroll-offset:56px] lg:[--scroll-offset:44px]"
|
117
115
|
|
118
116
|
#Don't actually do this!
|
119
|
-
@merger.merge(
|
117
|
+
@merger.merge("[padding:1rem] p-8") # → "[padding:1rem] p-8"
|
120
118
|
```
|
121
119
|
|
122
120
|
> **Warning** > `tailwind_merge` does not resolve conflicts between arbitrary properties and their matching Tailwind classes to keep the bundle size small.
|
@@ -124,11 +122,11 @@ The order of standard modifiers does not matter for tailwind-merge.
|
|
124
122
|
### Supports arbitrary variants
|
125
123
|
|
126
124
|
```ruby
|
127
|
-
@merger.merge(
|
128
|
-
@merger.merge(
|
125
|
+
@merger.merge("[&:nth-child(3)]:py-0 [&:nth-child(3)]:py-4") # → "[&:nth-child(3)]:py-4"
|
126
|
+
@merger.merge("dark:hover:[&:nth-child(3)]:py-0 hover:dark:[&:nth-child(3)]:py-4") # → "hover:dark:[&:nth-child(3)]:py-4"
|
129
127
|
|
130
128
|
# Don't actually do this!
|
131
|
-
@merger.merge(
|
129
|
+
@merger.merge("[&:focus]:ring focus:ring-4") # → "[&:focus]:ring focus:ring-4"
|
132
130
|
```
|
133
131
|
|
134
132
|
> **Warning**
|
@@ -138,26 +136,26 @@ The order of standard modifiers does not matter for tailwind-merge.
|
|
138
136
|
### Supports important modifier
|
139
137
|
|
140
138
|
```ruby
|
141
|
-
@merger.merge(
|
142
|
-
@merger.merge(
|
139
|
+
@merger.merge("!p-3 !p-4 p-5") # → "!p-4 p-5"
|
140
|
+
@merger.merge("!right-2 !-inset-x-1") # → "!-inset-x-1"
|
143
141
|
```
|
144
142
|
|
145
143
|
## Supports postfix modifiers
|
146
144
|
|
147
145
|
```ts
|
148
|
-
twMerge("text-sm leading-6 text-lg/7"); // →
|
146
|
+
twMerge("text-sm leading-6 text-lg/7"); // → "text-lg/7"
|
149
147
|
```
|
150
148
|
|
151
149
|
### Preserves non-Tailwind classes
|
152
150
|
|
153
151
|
```ruby
|
154
|
-
@merger.merge(
|
152
|
+
@merger.merge("p-5 p-2 my-non-tailwind-class p-4") # → "my-non-tailwind-class p-4"
|
155
153
|
```
|
156
154
|
|
157
155
|
### Supports custom colors out of the box
|
158
156
|
|
159
157
|
```ruby
|
160
|
-
@merger.merge(
|
158
|
+
@merger.merge("text-red text-secret-sauce") # → "text-secret-sauce"
|
161
159
|
```
|
162
160
|
|
163
161
|
## Basic usage
|
@@ -189,22 +187,24 @@ The `tailwind_merge` config is an object with several keys:
|
|
189
187
|
|
190
188
|
```ruby
|
191
189
|
tailwind_merge_config = {
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
190
|
+
# ↓ *Optional* Define how many values should be stored in cache.
|
191
|
+
cache_size: 500,
|
192
|
+
# ↓ *Optional* Enable or disable caching nil values.
|
193
|
+
ignore_empty_cache: true,
|
194
|
+
# ↓ *Optional* modifier separator from Tailwind config
|
195
|
+
separator: ":",
|
196
|
+
# ↓ *Optional* prefix from Tailwind config
|
197
|
+
prefix: "tw-",
|
198
|
+
theme: {
|
199
|
+
# Theme scales are defined here
|
200
|
+
# This is not the theme object from your Tailwind config
|
201
|
+
},
|
202
|
+
class_groups: {
|
203
|
+
# Class groups are defined here
|
204
|
+
},
|
205
|
+
conflicting_class_groups: {
|
206
|
+
# Conflicts between class groups are defined here
|
207
|
+
}
|
208
208
|
}
|
209
209
|
```
|
210
210
|
|
@@ -219,13 +219,13 @@ To use the custom configuration, pass it to the `TailwindMerge::Merger` initiali
|
|
219
219
|
The library uses a concept of _class groups_ which is an array of Tailwind classes which all modify the same CSS property. For example, here is the position class group:
|
220
220
|
|
221
221
|
```ruby
|
222
|
-
position_class_group = [
|
222
|
+
position_class_group = ["static", "fixed", "absolute", "relative", "sticky"]
|
223
223
|
```
|
224
224
|
|
225
225
|
`tailwind_merge` resolves conflicts between classes in a class group and only keeps the last one passed to the merge function call:
|
226
226
|
|
227
227
|
```ruby
|
228
|
-
@merger.merge(
|
228
|
+
@merger.merge("static sticky relative") # → "relative"
|
229
229
|
```
|
230
230
|
|
231
231
|
Tailwind classes often share the beginning of the class name, so elements in a class group can also be an object with values of the same shape as a class group (the shape is recursive). In the object, each key is joined with all the elements in the corresponding array with a dash (`-`) in between.
|
@@ -233,7 +233,7 @@ Tailwind classes often share the beginning of the class name, so elements in a c
|
|
233
233
|
For example, here is the overflow class group which results in the classes `overflow-auto`, `overflow-hidden`, `overflow-visible` and `overflow-scroll`.
|
234
234
|
|
235
235
|
```ruby
|
236
|
-
overflow_class_group = [{ overflow: [
|
236
|
+
overflow_class_group = [{ overflow: ["auto", "hidden", "visible", "scroll"] }]
|
237
237
|
```
|
238
238
|
|
239
239
|
Sometimes it isn't possible to enumerate every element in a class group. Think of a Tailwind class which allows arbitrary values. In this scenario you can use a validator function which takes a _class part_ and returns a boolean indicating whether a class is part of a class group.
|
@@ -242,16 +242,16 @@ For example, here is the fill class group:
|
|
242
242
|
|
243
243
|
```ruby
|
244
244
|
is_arbitrary_value = (class_part: string) => /^\[.+\]$/.test(class_part)
|
245
|
-
fill_class_group = [{ fill: [
|
245
|
+
fill_class_group = [{ fill: ["current", IS_ARBITRARY_VALUE] }]
|
246
246
|
```
|
247
247
|
|
248
248
|
Because the function is under the `fill` key, it will only get called for classes which start with `fill-`. Also, the function only gets passed the part of the class name which comes after `fill-`, this way you can use the same function in multiple class groups. `tailwind_merge` provides its own [validators](#validators), so you don't need to recreate them.
|
249
249
|
|
250
|
-
You can use an empty string (`
|
250
|
+
You can use an empty string (`""`) as a class part if you want to indicate that the preceding part was the end. This is useful for defining elements which are marked as `DEFAULT` in the Tailwind config.
|
251
251
|
|
252
252
|
```ruby
|
253
253
|
# ↓ Resolves to filter and filter-none
|
254
|
-
filter_class_group = [{ filter: [
|
254
|
+
filter_class_group = [{ filter: ["", "none"] }]
|
255
255
|
```
|
256
256
|
|
257
257
|
Each class group is defined under its ID in the `class_groups` object in the config. This ID is only used internally, and the only thing that matters is that it is unique among all class groups.
|
@@ -271,9 +271,7 @@ To summarize, `px-3` should stand in conflict with `pr-4`, but `pr-4` should not
|
|
271
271
|
This is what the `conflicting_class_groups` object in the config is for. You define a key in it which is the ID of a class group which _creates_ a conflict and the value is an array of IDs of class group which _receive_ a conflict.
|
272
272
|
|
273
273
|
```ruby
|
274
|
-
conflicting_class_groups = {
|
275
|
-
px: ['pr', 'pl'],
|
276
|
-
}
|
274
|
+
conflicting_class_groups = { px: ["pr", "pl"] }
|
277
275
|
```
|
278
276
|
|
279
277
|
If a class group _creates_ a conflict, it means that if it appears in a class list string passed to `merge`, all preceding class groups in the string which _receive_ the conflict will be removed.
|
@@ -314,10 +312,10 @@ If you modified one of these theme scales in your Tailwind config, you can add a
|
|
314
312
|
|
315
313
|
```ruby
|
316
314
|
merger = TailwindMerge::Merger.new(config: {
|
317
|
-
|
315
|
+
theme: {
|
318
316
|
"spacing" => ["my-space"],
|
319
|
-
"margin" => ["my-margin"]
|
320
|
-
|
317
|
+
"margin" => ["my-margin"]
|
318
|
+
}
|
321
319
|
})
|
322
320
|
```
|
323
321
|
|
@@ -16,7 +16,7 @@ module TailwindMerge
|
|
16
16
|
def class_group_id(class_name)
|
17
17
|
class_parts = class_name.split(CLASS_PART_SEPARATOR)
|
18
18
|
|
19
|
-
# Classes like `-inset-1` produce an empty string as first
|
19
|
+
# Classes like `-inset-1` produce an empty string as first class_part.
|
20
20
|
# Assume that classes for negative values are used correctly and remove it from class_parts.
|
21
21
|
class_parts.shift if class_parts.first == "" && class_parts.length != 1
|
22
22
|
|
@@ -30,9 +30,10 @@ module TailwindMerge
|
|
30
30
|
|
31
31
|
next_class_part_object = class_part_object[:next_part][current_class_part]
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
if next_class_part_object
|
34
|
+
class_group_from_next_class_part = get_group_recursive(class_parts.drop(1), next_class_part_object)
|
35
|
+
return class_group_from_next_class_part if class_group_from_next_class_part
|
36
|
+
end
|
36
37
|
|
37
38
|
return if class_part_object[:validators].empty?
|
38
39
|
|
@@ -40,29 +41,23 @@ module TailwindMerge
|
|
40
41
|
|
41
42
|
result = class_part_object[:validators].find do |v|
|
42
43
|
validator = v[:validator]
|
43
|
-
|
44
|
-
if from_theme?(validator)
|
45
|
-
validator.call(@config)
|
46
|
-
else
|
47
|
-
validator.call(class_rest)
|
48
|
-
end
|
44
|
+
from_theme?(validator) ? validator.call(@config) : validator.call(class_rest)
|
49
45
|
end
|
50
46
|
|
51
|
-
result
|
47
|
+
result&.fetch(:class_group_id, nil)
|
52
48
|
end
|
53
49
|
|
54
50
|
def get_conflicting_class_group_ids(class_group_id, has_postfix_modifier)
|
55
51
|
conflicts = @config[:conflicting_class_groups][class_group_id] || []
|
56
52
|
|
57
53
|
if has_postfix_modifier && @config[:conflicting_class_group_modifiers][class_group_id]
|
58
|
-
return [
|
54
|
+
return [*conflicts, *@config[:conflicting_class_group_modifiers][class_group_id]]
|
59
55
|
end
|
60
56
|
|
61
57
|
conflicts
|
62
58
|
end
|
63
59
|
|
64
60
|
private def create_class_map(config)
|
65
|
-
theme = config[:theme]
|
66
61
|
prefix = config[:prefix]
|
67
62
|
class_map = {
|
68
63
|
next_part: {},
|
@@ -70,12 +65,12 @@ module TailwindMerge
|
|
70
65
|
}
|
71
66
|
|
72
67
|
prefixed_class_group_entries = get_prefixed_class_group_entries(
|
73
|
-
config[:class_groups].map { |
|
68
|
+
config[:class_groups].map { |group_id, group_classes| [group_id, group_classes] },
|
74
69
|
prefix,
|
75
70
|
)
|
76
71
|
|
77
|
-
prefixed_class_group_entries.each do |
|
78
|
-
process_classes_recursively(class_group, class_map, class_group_id
|
72
|
+
prefixed_class_group_entries.each do |class_group_id, class_group|
|
73
|
+
process_classes_recursively(class_group, class_map, class_group_id)
|
79
74
|
end
|
80
75
|
|
81
76
|
class_map
|
@@ -84,53 +79,43 @@ module TailwindMerge
|
|
84
79
|
private def get_prefixed_class_group_entries(class_group_entries, prefix)
|
85
80
|
return class_group_entries if prefix.nil?
|
86
81
|
|
87
|
-
class_group_entries.map do |
|
82
|
+
class_group_entries.map do |class_group_id, class_group|
|
88
83
|
prefixed_class_group = class_group.map do |class_definition|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
94
91
|
end
|
95
92
|
|
96
93
|
[class_group_id, prefixed_class_group]
|
97
94
|
end
|
98
95
|
end
|
99
96
|
|
100
|
-
private def process_classes_recursively(class_group, class_part_object, class_group_id
|
97
|
+
private def process_classes_recursively(class_group, class_part_object, class_group_id)
|
101
98
|
class_group.each do |class_definition|
|
102
99
|
if class_definition.is_a?(String)
|
103
100
|
class_part_object_to_edit = class_definition.empty? ? class_part_object : get_class_part(class_part_object, class_definition)
|
104
101
|
class_part_object_to_edit[:class_group_id] = class_group_id
|
105
|
-
|
106
|
-
end
|
107
|
-
|
108
|
-
if class_definition.is_a?(Proc)
|
102
|
+
elsif class_definition.is_a?(Proc)
|
109
103
|
if from_theme?(class_definition)
|
104
|
+
process_classes_recursively(class_definition.call(@config), class_part_object, class_group_id)
|
105
|
+
else
|
106
|
+
class_part_object[:validators] << {
|
107
|
+
validator: class_definition,
|
108
|
+
class_group_id: class_group_id,
|
109
|
+
}
|
110
|
+
end
|
111
|
+
else
|
112
|
+
class_definition.each do |key, nested_class_group|
|
110
113
|
process_classes_recursively(
|
111
|
-
|
112
|
-
class_part_object,
|
114
|
+
nested_class_group,
|
115
|
+
get_class_part(class_part_object, key),
|
113
116
|
class_group_id,
|
114
|
-
theme,
|
115
117
|
)
|
116
|
-
next
|
117
118
|
end
|
118
|
-
|
119
|
-
class_part_object[:validators].push({
|
120
|
-
validator: class_definition,
|
121
|
-
class_group_id: class_group_id,
|
122
|
-
})
|
123
|
-
|
124
|
-
next
|
125
|
-
end
|
126
|
-
|
127
|
-
class_definition.each do |(key, class_group)|
|
128
|
-
process_classes_recursively(
|
129
|
-
class_group,
|
130
|
-
get_class_part(class_part_object, key),
|
131
|
-
class_group_id,
|
132
|
-
theme,
|
133
|
-
)
|
134
119
|
end
|
135
120
|
end
|
136
121
|
end
|
@@ -153,16 +138,13 @@ module TailwindMerge
|
|
153
138
|
end
|
154
139
|
|
155
140
|
private def get_group_id_for_arbitrary_property(class_name)
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
"arbitrary..#{property}"
|
164
|
-
end
|
165
|
-
end
|
141
|
+
match = ARBITRARY_PROPERTY_REGEX.match(class_name)
|
142
|
+
return unless match
|
143
|
+
|
144
|
+
property = match[1].to_s.split(":", 2).first
|
145
|
+
|
146
|
+
# Use two dots here because one dot is used as prefix for class groups in plugins
|
147
|
+
"arbitrary..#{property}" if property && !property.empty?
|
166
148
|
end
|
167
149
|
|
168
150
|
private def from_theme?(validator)
|
@@ -1,66 +1,43 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "set"
|
4
|
+
|
3
5
|
module TailwindMerge
|
4
6
|
module Config
|
5
7
|
include Validators
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
9
|
+
THEME_KEYS = [
|
10
|
+
"colors",
|
11
|
+
"spacing",
|
12
|
+
"blur",
|
13
|
+
"brightness",
|
14
|
+
"border-color",
|
15
|
+
"border-radius",
|
16
|
+
"border-spacing",
|
17
|
+
"border-width",
|
18
|
+
"contrast",
|
19
|
+
"grayscale",
|
20
|
+
"hue-rotate",
|
21
|
+
"invert",
|
22
|
+
"gap",
|
23
|
+
"gradient-color-stops",
|
24
|
+
"gradient-color-stop-positions",
|
25
|
+
"inset",
|
26
|
+
"margin",
|
27
|
+
"opacity",
|
28
|
+
"padding",
|
29
|
+
"saturate",
|
30
|
+
"scale",
|
31
|
+
"sepia",
|
32
|
+
"skew",
|
33
|
+
"space",
|
34
|
+
"translate",
|
35
|
+
].freeze
|
36
|
+
THEME_KEYS.each do |key|
|
37
|
+
const_set(key.upcase.tr("-", "_"), ->(config) { config[:theme].fetch(key, nil) })
|
38
|
+
end
|
36
39
|
|
37
|
-
VALID_THEME_IDS = Set.new(
|
38
|
-
COLORS.object_id,
|
39
|
-
SPACING.object_id,
|
40
|
-
BLUR.object_id,
|
41
|
-
BRIGHTNESS.object_id,
|
42
|
-
BORDER_COLOR.object_id,
|
43
|
-
BORDER_RADIUS.object_id,
|
44
|
-
BORDER_SPACING.object_id,
|
45
|
-
BORDER_WIDTH.object_id,
|
46
|
-
CONTRAST.object_id,
|
47
|
-
GRAYSCALE.object_id,
|
48
|
-
HUE_ROTATE.object_id,
|
49
|
-
INVERT.object_id,
|
50
|
-
GAP.object_id,
|
51
|
-
GRADIENT_COLOR_STOPS.object_id,
|
52
|
-
GRADIENT_COLOR_STOP_POSITIONS.object_id,
|
53
|
-
INSET.object_id,
|
54
|
-
MARGIN.object_id,
|
55
|
-
OPACITY.object_id,
|
56
|
-
PADDING.object_id,
|
57
|
-
SATURATE.object_id,
|
58
|
-
SCALE.object_id,
|
59
|
-
SEPIA.object_id,
|
60
|
-
SKEW.object_id,
|
61
|
-
SPACE.object_id,
|
62
|
-
TRANSLATE.object_id,
|
63
|
-
]).freeze
|
40
|
+
VALID_THEME_IDS = Set.new(THEME_KEYS.map { |theme_key| const_get(theme_key.upcase.tr("-", "_")).object_id }).freeze
|
64
41
|
|
65
42
|
OVERSCROLL = -> { ["auto", "contain", "none"] }
|
66
43
|
OVERFLOW = -> { ["auto", "hidden", "clip", "visible", "scroll"] }
|
@@ -109,6 +86,7 @@ module TailwindMerge
|
|
109
86
|
|
110
87
|
DEFAULTS = {
|
111
88
|
cache_size: 500,
|
89
|
+
ignore_empty_cache: true,
|
112
90
|
separator: ":",
|
113
91
|
theme: {
|
114
92
|
"colors" => [IS_ANY],
|
@@ -4,21 +4,20 @@ module TailwindMerge
|
|
4
4
|
module ModifierUtils
|
5
5
|
IMPORTANT_MODIFIER = "!"
|
6
6
|
|
7
|
-
def split_modifiers(class_name, separator:
|
8
|
-
separator ||= ":"
|
7
|
+
def split_modifiers(class_name, separator: ":")
|
9
8
|
separator_length = separator.length
|
10
|
-
|
11
|
-
|
9
|
+
separator_is_single_char = (separator_length == 1)
|
10
|
+
first_separator_char = separator[0]
|
12
11
|
|
13
12
|
modifiers = []
|
14
13
|
bracket_depth = 0
|
15
14
|
modifier_start = 0
|
16
|
-
postfix_modifier_position =
|
15
|
+
postfix_modifier_position = nil
|
17
16
|
|
18
17
|
class_name.each_char.with_index do |char, index|
|
19
18
|
if bracket_depth.zero?
|
20
|
-
if char ==
|
21
|
-
modifiers << class_name[modifier_start
|
19
|
+
if char == first_separator_char && (separator_is_single_char || class_name[index, separator_length] == separator)
|
20
|
+
modifiers << class_name[modifier_start...index]
|
22
21
|
modifier_start = index + separator_length
|
23
22
|
next
|
24
23
|
elsif char == "/"
|
@@ -27,17 +26,17 @@ module TailwindMerge
|
|
27
26
|
end
|
28
27
|
end
|
29
28
|
|
30
|
-
if char == "["
|
31
|
-
|
32
|
-
elsif char == "]"
|
33
|
-
bracket_depth -= 1
|
34
|
-
end
|
29
|
+
bracket_depth += 1 if char == "["
|
30
|
+
bracket_depth -= 1 if char == "]"
|
35
31
|
end
|
36
32
|
|
37
|
-
base_class_name_with_important_modifier = modifiers.empty? ? class_name : class_name[modifier_start
|
33
|
+
base_class_name_with_important_modifier = modifiers.empty? ? class_name : class_name[modifier_start..]
|
38
34
|
has_important_modifier = base_class_name_with_important_modifier.start_with?(IMPORTANT_MODIFIER)
|
39
|
-
base_class_name = has_important_modifier ? base_class_name_with_important_modifier[1
|
40
|
-
|
35
|
+
base_class_name = has_important_modifier ? base_class_name_with_important_modifier[1..] : base_class_name_with_important_modifier
|
36
|
+
|
37
|
+
maybe_postfix_modifier_position = if postfix_modifier_position && postfix_modifier_position > modifier_start
|
38
|
+
postfix_modifier_position - modifier_start
|
39
|
+
end
|
41
40
|
|
42
41
|
[modifiers, has_important_modifier, base_class_name, maybe_postfix_modifier_position]
|
43
42
|
end
|
@@ -46,25 +45,22 @@ module TailwindMerge
|
|
46
45
|
# - Predefined modifiers are sorted alphabetically
|
47
46
|
# - When an arbitrary variant appears, it must be preserved which modifiers are before and after it
|
48
47
|
def sort_modifiers(modifiers)
|
49
|
-
if modifiers.
|
50
|
-
return modifiers
|
51
|
-
end
|
48
|
+
return modifiers if modifiers.size <= 1
|
52
49
|
|
53
50
|
sorted_modifiers = []
|
54
51
|
unsorted_modifiers = []
|
55
52
|
|
56
53
|
modifiers.each do |modifier|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
unsorted_modifiers = []
|
54
|
+
if modifier.start_with?("[")
|
55
|
+
sorted_modifiers.concat(unsorted_modifiers.sort)
|
56
|
+
sorted_modifiers << modifier
|
57
|
+
unsorted_modifiers.clear
|
62
58
|
else
|
63
|
-
unsorted_modifiers
|
59
|
+
unsorted_modifiers << modifier
|
64
60
|
end
|
65
61
|
end
|
66
62
|
|
67
|
-
sorted_modifiers.
|
63
|
+
sorted_modifiers.concat(unsorted_modifiers.sort)
|
68
64
|
|
69
65
|
sorted_modifiers
|
70
66
|
end
|
@@ -8,12 +8,10 @@ module TailwindMerge
|
|
8
8
|
def arbitrary_value?(class_part, label, test_value)
|
9
9
|
match = ARBITRARY_VALUE_REGEX.match(class_part)
|
10
10
|
return false unless match
|
11
|
+
return test_value.call(match[2]) if match[1].nil?
|
12
|
+
return label == match[1] if label.is_a?(String)
|
11
13
|
|
12
|
-
|
13
|
-
return label.is_a?(Set) ? label.include?(match[1]) : label == match[1]
|
14
|
-
end
|
15
|
-
|
16
|
-
test_value.call(match[2])
|
14
|
+
label.include?(match[1])
|
17
15
|
end
|
18
16
|
|
19
17
|
def numeric?(x)
|
data/lib/tailwind_merge.rb
CHANGED
@@ -19,23 +19,21 @@ module TailwindMerge
|
|
19
19
|
SPLIT_CLASSES_REGEX = /\s+/
|
20
20
|
|
21
21
|
def initialize(config: {})
|
22
|
-
@config = if config.
|
22
|
+
@config = if config.key?(:theme)
|
23
23
|
merge_configs(config)
|
24
24
|
else
|
25
25
|
TailwindMerge::Config::DEFAULTS.merge(config)
|
26
26
|
end
|
27
27
|
|
28
28
|
@class_utils = TailwindMerge::ClassUtils.new(@config)
|
29
|
-
@cache = LruRedux::Cache.new(@config[:cache_size])
|
29
|
+
@cache = LruRedux::Cache.new(@config[:cache_size], @config[:ignore_empty_cache])
|
30
30
|
end
|
31
31
|
|
32
32
|
def merge(classes)
|
33
|
-
|
34
|
-
classes = classes.compact.join(" ")
|
35
|
-
end
|
33
|
+
normalized = classes.is_a?(Array) ? classes.compact.join(" ") : classes.to_s
|
36
34
|
|
37
|
-
@cache.getset(
|
38
|
-
merge_class_list(
|
35
|
+
@cache.getset(normalized) do
|
36
|
+
merge_class_list(normalized).freeze
|
39
37
|
end
|
40
38
|
end
|
41
39
|
|
@@ -45,37 +43,38 @@ module TailwindMerge
|
|
45
43
|
# @example 'float'
|
46
44
|
# @example 'hover:focus:bg-color'
|
47
45
|
# @example 'md:!pr'
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
result = ""
|
46
|
+
trimmed = class_list.strip
|
47
|
+
return "" if trimmed.empty?
|
52
48
|
|
53
|
-
|
49
|
+
class_groups_in_conflict = Set.new
|
54
50
|
|
55
|
-
|
56
|
-
break if i < 0
|
51
|
+
merged_classes = []
|
57
52
|
|
58
|
-
|
53
|
+
trimmed.split(SPLIT_CLASSES_REGEX).reverse_each do |original_class_name|
|
54
|
+
modifiers, has_important_modifier, base_class_name, maybe_postfix_modifier_position =
|
55
|
+
split_modifiers(original_class_name, separator: @config[:separator])
|
59
56
|
|
60
|
-
|
57
|
+
actual_base_class_name = if maybe_postfix_modifier_position
|
58
|
+
base_class_name[0...maybe_postfix_modifier_position]
|
59
|
+
else
|
60
|
+
base_class_name
|
61
|
+
end
|
61
62
|
|
62
|
-
|
63
|
+
has_postfix_modifier = maybe_postfix_modifier_position ? true : false
|
63
64
|
class_group_id = @class_utils.class_group_id(actual_base_class_name)
|
64
65
|
|
65
66
|
unless class_group_id
|
66
|
-
unless
|
67
|
-
#
|
68
|
-
|
69
|
-
i -= 1
|
67
|
+
unless has_postfix_modifier
|
68
|
+
# Not a Tailwind class
|
69
|
+
merged_classes << original_class_name
|
70
70
|
next
|
71
71
|
end
|
72
72
|
|
73
73
|
class_group_id = @class_utils.class_group_id(base_class_name)
|
74
74
|
|
75
75
|
unless class_group_id
|
76
|
-
#
|
77
|
-
|
78
|
-
i -= 1
|
76
|
+
# Not a Tailwind class
|
77
|
+
merged_classes << original_class_name
|
79
78
|
next
|
80
79
|
end
|
81
80
|
|
@@ -87,25 +86,20 @@ module TailwindMerge
|
|
87
86
|
modifier_id = has_important_modifier ? "#{variant_modifier}#{IMPORTANT_MODIFIER}" : variant_modifier
|
88
87
|
class_id = "#{modifier_id}#{class_group_id}"
|
89
88
|
|
90
|
-
# Tailwind class omitted due to
|
91
|
-
if class_groups_in_conflict.include?(class_id)
|
92
|
-
i -= 1
|
93
|
-
next
|
94
|
-
end
|
89
|
+
# Tailwind class omitted due to conflict
|
90
|
+
next if class_groups_in_conflict.include?(class_id)
|
95
91
|
|
96
|
-
class_groups_in_conflict
|
92
|
+
class_groups_in_conflict << class_id
|
97
93
|
|
98
|
-
@class_utils.get_conflicting_class_group_ids(class_group_id, has_postfix_modifier).each do |
|
99
|
-
class_groups_in_conflict
|
94
|
+
@class_utils.get_conflicting_class_group_ids(class_group_id, has_postfix_modifier).each do |conflicting_id|
|
95
|
+
class_groups_in_conflict << "#{modifier_id}#{conflicting_id}"
|
100
96
|
end
|
101
97
|
|
102
|
-
#
|
103
|
-
|
104
|
-
|
105
|
-
i -= 1
|
98
|
+
# Tailwind class not in conflict
|
99
|
+
merged_classes << original_class_name
|
106
100
|
end
|
107
101
|
|
108
|
-
|
102
|
+
merged_classes.reverse.join(" ")
|
109
103
|
end
|
110
104
|
end
|
111
105
|
end
|
data/tailwind_merge.gemspec
CHANGED
@@ -5,21 +5,24 @@ require_relative "lib/tailwind_merge/version"
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
6
|
spec.name = "tailwind_merge"
|
7
7
|
spec.version = TailwindMerge::VERSION
|
8
|
+
spec.summary = "Utility function to efficiently merge Tailwind CSS classes without style conflicts."
|
8
9
|
spec.authors = ["Garen J. Torikian"]
|
9
10
|
spec.email = ["gjtorikian@gmail.com"]
|
10
|
-
|
11
|
-
spec.summary = "Utility function to efficiently merge Tailwind CSS classes without style conflicts."
|
12
|
-
spec.homepage = "https://www.github.com/gjtorikian/tailwind_merge"
|
13
11
|
spec.license = "MIT"
|
14
12
|
|
15
|
-
|
16
|
-
|
13
|
+
github_root_uri = "https://github.com/gjtorikian/tailwind_merge"
|
14
|
+
spec.homepage = "#{github_root_uri}/tree/v#{spec.version}"
|
17
15
|
spec.metadata = {
|
18
|
-
"
|
16
|
+
"homepage_uri" => spec.homepage,
|
17
|
+
"source_code_uri" => spec.homepage,
|
18
|
+
"changelog_uri" => "#{github_root_uri}/blob/v#{spec.version}/CHANGELOG.md",
|
19
|
+
"bug_tracker_uri" => "#{github_root_uri}/issues",
|
20
|
+
"documentation_uri" => "https://rubydoc.info/gems/#{spec.name}/#{spec.version}",
|
21
|
+
"funding_uri" => "https://github.com/sponsors/gjtorikian",
|
19
22
|
"rubygems_mfa_required" => "true",
|
20
23
|
}
|
21
24
|
|
22
|
-
spec.
|
25
|
+
spec.required_ruby_version = [">= 3.1", "< 4.0"]
|
23
26
|
|
24
27
|
# Specify which files should be added to the gem when it is released.
|
25
28
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
@@ -32,7 +35,7 @@ Gem::Specification.new do |spec|
|
|
32
35
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
33
36
|
spec.require_paths = ["lib"]
|
34
37
|
|
35
|
-
spec.add_dependency("
|
38
|
+
spec.add_dependency("sin_lru_redux", "~> 2.5")
|
36
39
|
|
37
40
|
spec.add_development_dependency("minitest", "~> 5.6")
|
38
41
|
spec.add_development_dependency("minitest-focus", "~> 1.1")
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tailwind_merge
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.16.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Garen J. Torikian
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-01-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: sin_lru_redux
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '2.5'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '2.5'
|
27
27
|
force_ruby_platform: false
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: minitest
|
@@ -74,13 +74,17 @@ files:
|
|
74
74
|
- lib/tailwind_merge/validators.rb
|
75
75
|
- lib/tailwind_merge/version.rb
|
76
76
|
- tailwind_merge.gemspec
|
77
|
-
homepage: https://
|
77
|
+
homepage: https://github.com/gjtorikian/tailwind_merge/tree/v0.16.0
|
78
78
|
licenses:
|
79
79
|
- MIT
|
80
80
|
metadata:
|
81
|
-
|
81
|
+
homepage_uri: https://github.com/gjtorikian/tailwind_merge/tree/v0.16.0
|
82
|
+
source_code_uri: https://github.com/gjtorikian/tailwind_merge/tree/v0.16.0
|
83
|
+
changelog_uri: https://github.com/gjtorikian/tailwind_merge/blob/v0.16.0/CHANGELOG.md
|
84
|
+
bug_tracker_uri: https://github.com/gjtorikian/tailwind_merge/issues
|
85
|
+
documentation_uri: https://rubydoc.info/gems/tailwind_merge/0.16.0
|
86
|
+
funding_uri: https://github.com/sponsors/gjtorikian
|
82
87
|
rubygems_mfa_required: 'true'
|
83
|
-
homepage_uri: https://www.github.com/gjtorikian/tailwind_merge
|
84
88
|
post_install_message:
|
85
89
|
rdoc_options: []
|
86
90
|
require_paths:
|