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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3334600a104bc6b8efbe365c3be28e32944468f9cb5b1255fdefd4d106dd9540
4
- data.tar.gz: 2f8c9a9b989c3e808767516bff6b040ed0c3a092a5fc427aaacaae4904523bcc
3
+ metadata.gz: 991c169d4f9e2e472db150202e1a6c523a7f4698ef04d576557d0bfd25ffef0e
4
+ data.tar.gz: f7f0e832b00f4dfbcb61e1460a53f80d2c438354a412c953f7ab3a59a78d9fdd
5
5
  SHA512:
6
- metadata.gz: 7f8ad1a5e8dfc2386b81cf5f528e2b1b5ded8b9644dd942e777e3b356b0807d74397400c090b52aec68d54e93bc0bd8d672f9872795dd300b7e2b06b543701af
7
- data.tar.gz: 33cc99d39060f7f2e6827d48f99bce9989df5771f0174eea1994133a25e05115855de5e4f138b763f7197801595c3ab8e13802291675786f782f7246429079dd
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
- Supports Tailwind v3.0 up to v3.4.
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("!p-3 !p-4 p-5") # → "!p-4 p-5"
140
- @merger.merge("!right-2 !-inset-x-1") # → "!-inset-x-1"
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
- # ↓ *Optional* Define how many values should be stored in cache.
193
+ # Define how many values should be stored in cache.
191
194
  cache_size: 500,
192
- # ↓ *Optional* Enable or disable caching nil values.
195
+ # Enable or disable caching nil values.
193
196
  ignore_empty_cache: true,
194
- # *Optional* modifier separator from Tailwind config
195
- separator: ":",
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 scales. `tailwind_merge` follows the same keys for the theme scales, but doesn't support all of them. It only supports theme scales which are used in multiple class groups. At the moment these are:
284
-
285
- - `colors`
286
- - `spacing`
287
- - `blur`
288
- - `brightness`
289
- - `borderColor`
290
- - `borderRadius`
291
- - `borderSpacing`
292
- - `borderWidth`
293
- - `contrast`
294
- - `grayscale`
295
- - `hueRotate`
296
- - `invert`
297
- - `gap`
298
- - `gradientColorStops`
299
- - `gradientColorStopPositions`
300
- - `inset`
301
- - `margin`
302
- - `opacity`
303
- - `padding`
304
- - `saturate`
305
- - `scale`
306
- - `sepia`
307
- - `skew`
308
- - `space`
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
- theme: {
316
- "spacing" => ["my-space"],
317
- "margin" => ["my-margin"]
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
- - `IS_LENGTH` checks whether a class part is a number (`3`, `1.5`), a fraction (`3/4`), or one of the strings `px`, `full` or `screen`.
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 ClassUtils
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
- prefix = config[:prefix]
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
- prefixed_class_group_entries = get_prefixed_class_group_entries(
68
- config[:class_groups].map { |group_id, group_classes| [group_id, group_classes] },
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 get_prefixed_class_group_entries(class_group_entries, prefix)
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