tailwind_merge 0.15.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9e79ce87f36904f2dd66beecff8d32e83b97025d729f6c324f14da53aca7c8e
4
- data.tar.gz: 6ca2a49f5f9039028a9f6677bca5ec2f8765c52887680dd41c47449fe1770f28
3
+ metadata.gz: 3334600a104bc6b8efbe365c3be28e32944468f9cb5b1255fdefd4d106dd9540
4
+ data.tar.gz: 2f8c9a9b989c3e808767516bff6b040ed0c3a092a5fc427aaacaae4904523bcc
5
5
  SHA512:
6
- metadata.gz: 20b3f51eb10c68b29b5f4354570ba6cdfbcab13103d7a45f213aecdaaaa50f5c9876b676f6a217c4b0a98335cffce007f1926f0f74b2c98af0155174b48c13d2
7
- data.tar.gz: fe5786fa019198ca80233bceec9acdd5d9bd6d8098907143600d1df1e5a7a2b6525daa329c146c6d741c1df0dd276f7a87734f718cd865b7ad734d374bc6feed
6
+ metadata.gz: 7f8ad1a5e8dfc2386b81cf5f528e2b1b5ded8b9644dd942e777e3b356b0807d74397400c090b52aec68d54e93bc0bd8d672f9872795dd300b7e2b06b543701af
7
+ data.tar.gz: 33cc99d39060f7f2e6827d48f99bce9989df5771f0174eea1994133a25e05115855de5e4f138b763f7197801595c3ab8e13802291675786f782f7246429079dd
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
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
1
7
  # [v0.15.0] - 23-01-2025
2
8
  ## What's Changed
3
9
  * Improve gemspec by @w-masahiro-ct in https://github.com/gjtorikian/tailwind_merge/pull/41
@@ -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 classPart.
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
- class_group_from_next_class_part = next_class_part_object ? get_group_recursive(class_parts[1..-1], next_class_part_object) : nil
34
-
35
- return class_group_from_next_class_part if class_group_from_next_class_part
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.nil? ? result : result[:class_group_id]
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 [...conflicts, ...@config[:conflicting_class_group_modifiers][class_group_id]]
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 { |cg| [cg[0], cg[1]] },
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 |(class_group_id, class_group)|
78
- process_classes_recursively(class_group, class_map, class_group_id, theme)
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 |(class_group_id, class_group)|
82
+ class_group_entries.map do |class_group_id, class_group|
88
83
  prefixed_class_group = class_group.map do |class_definition|
89
- next("#{prefix}#{class_definition}") if class_definition.is_a?(String)
90
-
91
- next(class_definition.transform_keys { |key| "#{prefix}#{key}" }) if class_definition.is_a?(Hash)
92
-
93
- 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
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, theme)
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
- next
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
- class_definition.call(@config),
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
- if ARBITRARY_PROPERTY_REGEX.match?(class_name)
157
- match = ARBITRARY_PROPERTY_REGEX.match(class_name) || ""
158
- arbitrary_property_class_name = match[1] || ""
159
- property = arbitrary_property_class_name[0...arbitrary_property_class_name.index(":")]
160
-
161
- if !property.nil? && !property.empty?
162
- # uses two dots here because one dot is used as prefix for class groups in plugins
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,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "set"
4
+
3
5
  module TailwindMerge
4
6
  module Config
5
7
  include Validators
@@ -30,7 +32,7 @@ module TailwindMerge
30
32
  "skew",
31
33
  "space",
32
34
  "translate",
33
- ]
35
+ ].freeze
34
36
  THEME_KEYS.each do |key|
35
37
  const_set(key.upcase.tr("-", "_"), ->(config) { config[:theme].fetch(key, nil) })
36
38
  end
@@ -4,21 +4,20 @@ module TailwindMerge
4
4
  module ModifierUtils
5
5
  IMPORTANT_MODIFIER = "!"
6
6
 
7
- def split_modifiers(class_name, separator: nil)
8
- separator ||= ":"
7
+ def split_modifiers(class_name, separator: ":")
9
8
  separator_length = separator.length
10
- seperator_is_single_char = separator_length == 1
11
- first_seperator_char = separator[0]
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 = 0
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 == first_seperator_char && (seperator_is_single_char || class_name[index..(index + separator_length - 1)] == separator)
21
- modifiers << class_name[modifier_start..index]
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
- bracket_depth += 1
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..-1]
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..-1] : base_class_name_with_important_modifier
40
- maybe_postfix_modifier_position = postfix_modifier_position && postfix_modifier_position > modifier_start ? postfix_modifier_position - modifier_start : false
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.length <= 1
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
- is_arbitrary_variant = modifier[0] == "["
58
-
59
- if is_arbitrary_variant
60
- sorted_modifiers.push(unsorted_modifiers.sort, modifier)
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.push(modifier)
59
+ unsorted_modifiers << modifier
64
60
  end
65
61
  end
66
62
 
67
- sorted_modifiers.push(...unsorted_modifiers.sort)
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
- unless match[1].nil?
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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TailwindMerge
4
- VERSION = "0.15.0"
4
+ VERSION = "0.16.0"
5
5
  end
@@ -19,7 +19,7 @@ module TailwindMerge
19
19
  SPLIT_CLASSES_REGEX = /\s+/
20
20
 
21
21
  def initialize(config: {})
22
- @config = if config.fetch(:theme, nil)
22
+ @config = if config.key?(:theme)
23
23
  merge_configs(config)
24
24
  else
25
25
  TailwindMerge::Config::DEFAULTS.merge(config)
@@ -30,12 +30,10 @@ module TailwindMerge
30
30
  end
31
31
 
32
32
  def merge(classes)
33
- if classes.is_a?(Array)
34
- classes = classes.compact.join(" ")
35
- end
33
+ normalized = classes.is_a?(Array) ? classes.compact.join(" ") : classes.to_s
36
34
 
37
- @cache.getset(classes) do
38
- merge_class_list(classes).freeze
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
- class_groups_in_conflict = []
49
- class_names = class_list.strip.split(SPLIT_CLASSES_REGEX)
50
-
51
- result = ""
46
+ trimmed = class_list.strip
47
+ return "" if trimmed.empty?
52
48
 
53
- i = class_names.length - 1
49
+ class_groups_in_conflict = Set.new
54
50
 
55
- loop do
56
- break if i < 0
51
+ merged_classes = []
57
52
 
58
- original_class_name = class_names[i]
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
- modifiers, has_important_modifier, base_class_name, maybe_postfix_modifier_position = split_modifiers(original_class_name, separator: @config[:separator])
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
- actual_base_class_name = maybe_postfix_modifier_position ? base_class_name[0...maybe_postfix_modifier_position] : base_class_name
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 maybe_postfix_modifier_position
67
- # not a Tailwind class
68
- result = original_class_name + (!result.empty? ? " " + result : result)
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
- # not a Tailwind class
77
- result = original_class_name + (!result.empty? ? " " + result : result)
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 pre-existing conflict
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.push(class_id)
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 |group|
99
- class_groups_in_conflict.push("#{modifier_id}#{group}")
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
- # no conflict!
103
- result = original_class_name + (!result.empty? ? " " + result : result)
104
-
105
- i -= 1
98
+ # Tailwind class not in conflict
99
+ merged_classes << original_class_name
106
100
  end
107
101
 
108
- result
102
+ merged_classes.reverse.join(" ")
109
103
  end
110
104
  end
111
105
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tailwind_merge
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.15.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: 2025-01-23 00:00:00.000000000 Z
11
+ date: 2025-01-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sin_lru_redux
@@ -74,15 +74,15 @@ files:
74
74
  - lib/tailwind_merge/validators.rb
75
75
  - lib/tailwind_merge/version.rb
76
76
  - tailwind_merge.gemspec
77
- homepage: https://github.com/gjtorikian/tailwind_merge/tree/v0.15.0
77
+ homepage: https://github.com/gjtorikian/tailwind_merge/tree/v0.16.0
78
78
  licenses:
79
79
  - MIT
80
80
  metadata:
81
- homepage_uri: https://github.com/gjtorikian/tailwind_merge/tree/v0.15.0
82
- source_code_uri: https://github.com/gjtorikian/tailwind_merge/tree/v0.15.0
83
- changelog_uri: https://github.com/gjtorikian/tailwind_merge/blob/v0.15.0/CHANGELOG.md
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
84
  bug_tracker_uri: https://github.com/gjtorikian/tailwind_merge/issues
85
- documentation_uri: https://rubydoc.info/gems/tailwind_merge/0.15.0
85
+ documentation_uri: https://rubydoc.info/gems/tailwind_merge/0.16.0
86
86
  funding_uri: https://github.com/sponsors/gjtorikian
87
87
  rubygems_mfa_required: 'true'
88
88
  post_install_message: