tailwind_merge 0.14.0 → 0.16.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 +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:
|