tailwind_merge 0.16.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +84 -54
- data/lib/tailwind_merge/{class_utils.rb → class_group_utils.rb} +8 -29
- data/lib/tailwind_merge/config.rb +723 -418
- data/lib/tailwind_merge/parse_class_name.rb +89 -0
- data/lib/tailwind_merge/sort_modifiers.rb +31 -0
- data/lib/tailwind_merge/validators.rb +109 -45
- data/lib/tailwind_merge/version.rb +1 -1
- data/lib/tailwind_merge.rb +20 -18
- data/script/test +43 -0
- metadata +11 -9
- data/lib/tailwind_merge/modifier_utils.rb +0 -68
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TailwindMerge
|
4
|
+
class TailwindClass < Struct.new(:is_external, :modifiers, :has_important_modifier, :base_class_name, :maybe_postfix_modifier_position)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ParseClassName
|
8
|
+
IMPORTANT_MODIFIER = "!"
|
9
|
+
MODIFIER_SEPARATOR = ":"
|
10
|
+
MODIFIER_SEPARATOR_LENGTH = MODIFIER_SEPARATOR.length
|
11
|
+
|
12
|
+
##
|
13
|
+
# Parse class name into parts.
|
14
|
+
#
|
15
|
+
# Inspired by `splitAtTopLevelOnly` used in Tailwind CSS
|
16
|
+
# @see https://github.com/tailwindlabs/tailwindcss/blob/v3.2.2/src/util/splitAtTopLevelOnly.js
|
17
|
+
def parse_class_name(class_name, prefix: nil)
|
18
|
+
unless prefix.nil?
|
19
|
+
full_prefix = "#{prefix}#{MODIFIER_SEPARATOR}"
|
20
|
+
if class_name.start_with?(full_prefix)
|
21
|
+
return parse_class_name(class_name[full_prefix.length..])
|
22
|
+
else
|
23
|
+
return TailwindClass.new(
|
24
|
+
is_external: true,
|
25
|
+
modifiers: [],
|
26
|
+
has_important_modifier: false,
|
27
|
+
base_class_name: class_name,
|
28
|
+
maybe_postfix_modifier_position: nil,
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
modifiers = []
|
34
|
+
|
35
|
+
bracket_depth = 0
|
36
|
+
paren_depth = 0
|
37
|
+
modifier_start = 0
|
38
|
+
postfix_modifier_position = nil
|
39
|
+
|
40
|
+
class_name.each_char.with_index do |char, index|
|
41
|
+
if bracket_depth.zero? && paren_depth.zero?
|
42
|
+
if char == MODIFIER_SEPARATOR
|
43
|
+
modifiers << class_name[modifier_start...index]
|
44
|
+
modifier_start = index + MODIFIER_SEPARATOR_LENGTH
|
45
|
+
next
|
46
|
+
elsif char == "/"
|
47
|
+
postfix_modifier_position = index
|
48
|
+
next
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
bracket_depth += 1 if char == "["
|
53
|
+
bracket_depth -= 1 if char == "]"
|
54
|
+
paren_depth += 1 if char == "("
|
55
|
+
paren_depth -= 1 if char == ")"
|
56
|
+
end
|
57
|
+
|
58
|
+
base_class_name_with_important_modifier = modifiers.empty? ? class_name : class_name[modifier_start..]
|
59
|
+
|
60
|
+
base_class_name, has_important_modifier = strip_important_modifier(base_class_name_with_important_modifier)
|
61
|
+
|
62
|
+
maybe_postfix_modifier_position = if postfix_modifier_position && postfix_modifier_position > modifier_start
|
63
|
+
postfix_modifier_position - modifier_start
|
64
|
+
end
|
65
|
+
|
66
|
+
TailwindClass.new(
|
67
|
+
is_external: false,
|
68
|
+
modifiers:,
|
69
|
+
has_important_modifier:,
|
70
|
+
base_class_name: base_class_name,
|
71
|
+
maybe_postfix_modifier_position:,
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def strip_important_modifier(base_class_name)
|
76
|
+
if base_class_name.end_with?(IMPORTANT_MODIFIER)
|
77
|
+
return [base_class_name[0...-IMPORTANT_MODIFIER.length], true]
|
78
|
+
end
|
79
|
+
|
80
|
+
# In Tailwind CSS v3 the important modifier was at the start of the base class name. This is still supported for legacy reasons.
|
81
|
+
# @see https://github.com/dcastil/tailwind-merge/issues/513#issuecomment-2614029864
|
82
|
+
if base_class_name.start_with?(IMPORTANT_MODIFIER)
|
83
|
+
return [base_class_name[1..], true]
|
84
|
+
end
|
85
|
+
|
86
|
+
[base_class_name, false]
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TailwindMerge
|
4
|
+
module SortModifiers
|
5
|
+
# Sorts modifiers according to following schema:
|
6
|
+
# - Predefined modifiers are sorted alphabetically
|
7
|
+
# - When an arbitrary variant appears, it must be preserved which modifiers are before and after it
|
8
|
+
def sort_modifiers(modifiers, order_sensitive_modifiers)
|
9
|
+
return modifiers if modifiers.size <= 1
|
10
|
+
|
11
|
+
sorted_modifiers = []
|
12
|
+
unsorted_modifiers = []
|
13
|
+
|
14
|
+
modifiers.each do |modifier|
|
15
|
+
is_position_sensitive = modifier.start_with?("[") || order_sensitive_modifiers.include?(modifier)
|
16
|
+
|
17
|
+
if is_position_sensitive
|
18
|
+
sorted_modifiers.concat(unsorted_modifiers.sort)
|
19
|
+
sorted_modifiers << modifier
|
20
|
+
unsorted_modifiers.clear
|
21
|
+
else
|
22
|
+
unsorted_modifiers << modifier
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
sorted_modifiers.concat(unsorted_modifiers.sort)
|
27
|
+
|
28
|
+
sorted_modifiers
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -5,13 +5,22 @@ require "set"
|
|
5
5
|
module TailwindMerge
|
6
6
|
module Validators
|
7
7
|
class << self
|
8
|
-
def arbitrary_value?(class_part,
|
8
|
+
def arbitrary_value?(class_part, test_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)
|
13
11
|
|
14
|
-
|
12
|
+
return test_label.call(match[1]) unless match[1].nil?
|
13
|
+
|
14
|
+
test_value.call(match[2])
|
15
|
+
end
|
16
|
+
|
17
|
+
def arbitrary_variable?(class_part, test_label, should_match_no_label: false)
|
18
|
+
match = ARBITRARY_VARIABLE_REGEX.match(class_part)
|
19
|
+
return false unless match
|
20
|
+
|
21
|
+
return test_label.call(match[1]) unless match[1].nil?
|
22
|
+
|
23
|
+
should_match_no_label
|
15
24
|
end
|
16
25
|
|
17
26
|
def numeric?(x)
|
@@ -23,95 +32,150 @@ module TailwindMerge
|
|
23
32
|
end
|
24
33
|
end
|
25
34
|
|
26
|
-
|
27
|
-
|
28
|
-
ARBITRARY_VALUE_REGEX = /^\[(?:([a-z-]+):)?(.+)\]$/i
|
35
|
+
ARBITRARY_VALUE_REGEX = /^\[(?:(\w[\w-]*):)?(.+)\]$/i
|
36
|
+
ARBITRARY_VARIABLE_REGEX = /^\((?:(\w[\w-]*):)?(.+)\)$/i
|
29
37
|
FRACTION_REGEX = %r{^\d+/\d+$}
|
30
|
-
LENGTH_UNIT_REGEX = /\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh|cq(w|h|i|b|min|max))|\b(calc|min|max|clamp)\(.+\)|^0$/
|
31
38
|
TSHIRT_UNIT_REGEX = /^(\d+(\.\d+)?)?(xs|sm|md|lg|xl)$/
|
39
|
+
LENGTH_UNIT_REGEX = /\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh|cq(w|h|i|b|min|max))|\b(calc|min|max|clamp)\(.+\)|^0$/
|
32
40
|
COLOR_FUNCTION_REGEX = /^(rgba?|hsla?|hwb|(ok)?(lab|lch))\(.+\)$/
|
41
|
+
|
33
42
|
# Shadow always begins with x and y offset separated by underscore optionally prepended by inset
|
34
43
|
SHADOW_REGEX = /^(inset_)?-?((\d+)?\.?(\d+)[a-z]+|0)_-?((\d+)?\.?(\d+)[a-z]+|0)/
|
35
44
|
IMAGE_REGEX = /^(url|image|image-set|cross-fade|element|(repeating-)?(linear|radial|conic)-gradient)\(.+\)$/
|
36
45
|
|
37
|
-
|
38
|
-
|
46
|
+
IS_FRACTION = ->(value) {
|
47
|
+
FRACTION_REGEX.match?(value)
|
48
|
+
}
|
49
|
+
|
50
|
+
IS_NUMBER = ->(value) {
|
51
|
+
numeric?(value)
|
52
|
+
}
|
53
|
+
|
54
|
+
IS_INTEGER = ->(value) {
|
55
|
+
integer?(value)
|
56
|
+
}
|
57
|
+
|
58
|
+
IS_PERCENT = ->(value) {
|
59
|
+
value.end_with?("%") && IS_NUMBER.call(value[0..-2])
|
60
|
+
}
|
61
|
+
|
62
|
+
IS_TSHIRT_SIZE = ->(value) {
|
63
|
+
TSHIRT_UNIT_REGEX.match?(value)
|
64
|
+
}
|
65
|
+
|
66
|
+
IS_ANY = ->(_ = nil) { true }
|
39
67
|
|
40
|
-
|
68
|
+
IS_LENGTH_ONLY = ->(value) {
|
41
69
|
# `colorFunctionRegex` check is necessary because color functions can have percentages in them which which would be incorrectly classified as lengths.
|
42
70
|
# For example, `hsl(0 0% 0%)` would be classified as a length without this check.
|
43
71
|
# I could also use lookbehind assertion in `lengthUnitRegex` but that isn't supported widely enough.
|
44
72
|
LENGTH_UNIT_REGEX.match?(value) && !COLOR_FUNCTION_REGEX.match?(value)
|
45
73
|
}
|
46
74
|
|
47
|
-
|
75
|
+
IS_NEVER = ->(_) { false }
|
48
76
|
|
49
|
-
|
50
|
-
|
77
|
+
IS_SHADOW = ->(value) {
|
78
|
+
SHADOW_REGEX.match?(value)
|
51
79
|
}
|
52
80
|
|
53
|
-
|
54
|
-
|
81
|
+
IS_IMAGE = ->(value) {
|
82
|
+
IMAGE_REGEX.match?(value)
|
55
83
|
}
|
56
84
|
|
57
|
-
|
58
|
-
|
85
|
+
IS_ANY_NON_ARBITRARY = ->(value) {
|
86
|
+
!IS_ARBITRARY_VALUE.call(value) && !IS_ARBITRARY_VARIABLE.call(value)
|
59
87
|
}
|
60
88
|
|
61
|
-
|
62
|
-
|
89
|
+
IS_ARBITRARY_SIZE = ->(value) {
|
90
|
+
arbitrary_value?(value, IS_LABEL_SIZE, IS_NEVER)
|
63
91
|
}
|
64
92
|
|
65
|
-
|
66
|
-
|
67
|
-
STRING_LENGTHS.include?(value) ||
|
68
|
-
FRACTION_REGEX.match?(value)
|
93
|
+
IS_ARBITRARY_VALUE = ->(value) {
|
94
|
+
ARBITRARY_VALUE_REGEX.match(value)
|
69
95
|
}
|
70
96
|
|
71
97
|
IS_ARBITRARY_LENGTH = ->(value) {
|
72
|
-
arbitrary_value?(value,
|
98
|
+
arbitrary_value?(value, IS_LABEL_LENGTH, IS_LENGTH_ONLY)
|
73
99
|
}
|
74
100
|
|
75
101
|
IS_ARBITRARY_NUMBER = ->(value) {
|
76
|
-
arbitrary_value?(value,
|
102
|
+
arbitrary_value?(value, IS_LABEL_NUMBER, IS_NUMBER)
|
77
103
|
}
|
78
104
|
|
79
|
-
|
80
|
-
|
105
|
+
IS_ARBITRARY_POSITION = ->(value) {
|
106
|
+
arbitrary_value?(value, IS_LABEL_POSITION, IS_NEVER)
|
81
107
|
}
|
82
108
|
|
83
|
-
|
84
|
-
|
109
|
+
IS_ARBITRARY_IMAGE = ->(value) {
|
110
|
+
arbitrary_value?(value, IS_LABEL_IMAGE, IS_IMAGE)
|
85
111
|
}
|
86
112
|
|
87
|
-
|
88
|
-
|
113
|
+
IS_ARBITRARY_SHADOW = ->(value) {
|
114
|
+
arbitrary_value?(value, IS_NEVER, IS_SHADOW)
|
89
115
|
}
|
90
116
|
|
91
|
-
|
92
|
-
|
117
|
+
IS_ARBITRARY_VARIABLE = ->(value) {
|
118
|
+
ARBITRARY_VARIABLE_REGEX.match(value)
|
93
119
|
}
|
94
120
|
|
95
|
-
|
96
|
-
|
121
|
+
IS_ARBITRARY_VARIABLE_LENGTH = ->(value) {
|
122
|
+
arbitrary_variable?(value, IS_LABEL_LENGTH)
|
97
123
|
}
|
98
124
|
|
99
|
-
|
100
|
-
|
125
|
+
IS_ARBITRARY_VARIABLE_FAMILY_NAME = ->(value) {
|
126
|
+
arbitrary_variable?(value, IS_LABEL_FAMILY_NAME)
|
101
127
|
}
|
102
128
|
|
103
|
-
|
104
|
-
|
129
|
+
IS_ARBITRARY_VARIABLE_POSITION = ->(value) {
|
130
|
+
arbitrary_variable?(value, IS_LABEL_POSITION)
|
105
131
|
}
|
106
132
|
|
107
|
-
|
108
|
-
|
133
|
+
IS_ARBITRARY_VARIABLE_SIZE = ->(value) {
|
134
|
+
arbitrary_variable?(value, IS_LABEL_SIZE)
|
109
135
|
}
|
110
136
|
|
111
|
-
|
112
|
-
|
137
|
+
IS_ARBITRARY_VARIABLE_IMAGE = ->(value) {
|
138
|
+
arbitrary_variable?(value, IS_LABEL_IMAGE)
|
139
|
+
}
|
140
|
+
|
141
|
+
IS_ARBITRARY_VARIABLE_SHADOW = ->(value) {
|
142
|
+
arbitrary_variable?(value, IS_LABEL_SHADOW, should_match_no_label: true)
|
113
143
|
}
|
114
144
|
|
115
|
-
|
145
|
+
############
|
146
|
+
# Labels
|
147
|
+
############
|
148
|
+
|
149
|
+
IS_LABEL_POSITION = ->(value) {
|
150
|
+
value == "position"
|
151
|
+
}
|
152
|
+
|
153
|
+
IMAGE_LABELS = Set.new(["image", "url"]).freeze
|
154
|
+
|
155
|
+
IS_LABEL_IMAGE = ->(value) {
|
156
|
+
IMAGE_LABELS.include?(value)
|
157
|
+
}
|
158
|
+
|
159
|
+
SIZE_LABELS = Set.new(["length", "size", "percentage"]).freeze
|
160
|
+
|
161
|
+
IS_LABEL_SIZE = ->(value) {
|
162
|
+
SIZE_LABELS.include?(value)
|
163
|
+
}
|
164
|
+
|
165
|
+
IS_LABEL_LENGTH = ->(value) {
|
166
|
+
value == "length"
|
167
|
+
}
|
168
|
+
|
169
|
+
IS_LABEL_NUMBER = ->(value) {
|
170
|
+
value == "number"
|
171
|
+
}
|
172
|
+
|
173
|
+
IS_LABEL_FAMILY_NAME = ->(value) {
|
174
|
+
value == "family-name"
|
175
|
+
}
|
176
|
+
|
177
|
+
IS_LABEL_SHADOW = ->(value) {
|
178
|
+
value == "shadow"
|
179
|
+
}
|
116
180
|
end
|
117
181
|
end
|
data/lib/tailwind_merge.rb
CHANGED
@@ -5,8 +5,9 @@ require "lru_redux"
|
|
5
5
|
require_relative "tailwind_merge/version"
|
6
6
|
require_relative "tailwind_merge/validators"
|
7
7
|
require_relative "tailwind_merge/config"
|
8
|
-
require_relative "tailwind_merge/
|
9
|
-
require_relative "tailwind_merge/
|
8
|
+
require_relative "tailwind_merge/class_group_utils"
|
9
|
+
require_relative "tailwind_merge/sort_modifiers"
|
10
|
+
require_relative "tailwind_merge/parse_class_name"
|
10
11
|
|
11
12
|
require "strscan"
|
12
13
|
require "set"
|
@@ -14,18 +15,15 @@ require "set"
|
|
14
15
|
module TailwindMerge
|
15
16
|
class Merger
|
16
17
|
include Config
|
17
|
-
include
|
18
|
+
include ParseClassName
|
19
|
+
include SortModifiers
|
18
20
|
|
19
21
|
SPLIT_CLASSES_REGEX = /\s+/
|
20
22
|
|
21
23
|
def initialize(config: {})
|
22
|
-
@config =
|
23
|
-
|
24
|
-
|
25
|
-
TailwindMerge::Config::DEFAULTS.merge(config)
|
26
|
-
end
|
27
|
-
|
28
|
-
@class_utils = TailwindMerge::ClassUtils.new(@config)
|
24
|
+
@config = merge_config(config)
|
25
|
+
@config[:important_modifier] = @config[:important_modifier].to_s
|
26
|
+
@class_utils = TailwindMerge::ClassGroupUtils.new(@config)
|
29
27
|
@cache = LruRedux::Cache.new(@config[:cache_size], @config[:ignore_empty_cache])
|
30
28
|
end
|
31
29
|
|
@@ -51,16 +49,20 @@ module TailwindMerge
|
|
51
49
|
merged_classes = []
|
52
50
|
|
53
51
|
trimmed.split(SPLIT_CLASSES_REGEX).reverse_each do |original_class_name|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
52
|
+
result = parse_class_name(original_class_name, prefix: @config[:prefix])
|
53
|
+
is_external = result.is_external
|
54
|
+
modifiers = result.modifiers
|
55
|
+
has_important_modifier = result.has_important_modifier
|
56
|
+
base_class_name = result.base_class_name
|
57
|
+
maybe_postfix_modifier_position = result.maybe_postfix_modifier_position
|
58
|
+
|
59
|
+
if is_external
|
60
|
+
merged_classes.push(original_class_name)
|
61
|
+
next
|
61
62
|
end
|
62
63
|
|
63
64
|
has_postfix_modifier = maybe_postfix_modifier_position ? true : false
|
65
|
+
actual_base_class_name = has_postfix_modifier ? base_class_name[0...maybe_postfix_modifier_position] : base_class_name
|
64
66
|
class_group_id = @class_utils.class_group_id(actual_base_class_name)
|
65
67
|
|
66
68
|
unless class_group_id
|
@@ -81,7 +83,7 @@ module TailwindMerge
|
|
81
83
|
has_postfix_modifier = false
|
82
84
|
end
|
83
85
|
|
84
|
-
variant_modifier = sort_modifiers(modifiers).join(":")
|
86
|
+
variant_modifier = sort_modifiers(modifiers, @config[:order_sensitive_modifiers]).join(":")
|
85
87
|
|
86
88
|
modifier_id = has_important_modifier ? "#{variant_modifier}#{IMPORTANT_MODIFIER}" : variant_modifier
|
87
89
|
class_id = "#{modifier_id}#{class_group_id}"
|
data/script/test
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
#/ Usage: script/test <filename:test_line>
|
3
|
+
#/ 1. `script/test FILE` runs all tests in the file.
|
4
|
+
#/ 1. `script/test FILE:LINE` runs test in specific line of the file.
|
5
|
+
#/ 1. `script/test 'GLOB'` runs all tests for matching glob.
|
6
|
+
#/ * make sure to wrap the `GLOB` in single quotes `''`.
|
7
|
+
|
8
|
+
if ! [ $# -eq 0 ]; then
|
9
|
+
export TEST=$(echo "$1" | cut -d ":" -f 1)
|
10
|
+
|
11
|
+
if [[ "$1" == *":"* ]]; then
|
12
|
+
LINE=$(echo "$1" | cut -d ":" -f 2)
|
13
|
+
LINE=$(head -n $LINE $TEST | tail -1)
|
14
|
+
NAME=$(echo "$LINE" | sed "s/.*def //")
|
15
|
+
|
16
|
+
if ! [[ "$NAME" == "test_"* ]] && ! [[ "$NAME" == "bench_"* ]]; then
|
17
|
+
echo
|
18
|
+
echo "ERROR: Line provided does not define a test case"
|
19
|
+
exit 1
|
20
|
+
fi
|
21
|
+
|
22
|
+
export TESTOPTS="--name=$NAME"
|
23
|
+
fi
|
24
|
+
fi
|
25
|
+
|
26
|
+
# Check for and parse flags
|
27
|
+
for arg in "$@"
|
28
|
+
do
|
29
|
+
case $arg in
|
30
|
+
--debug)
|
31
|
+
export DEBUG=true
|
32
|
+
;;
|
33
|
+
*)
|
34
|
+
# Unknown arguments can be ignored or handled here
|
35
|
+
;;
|
36
|
+
esac
|
37
|
+
done
|
38
|
+
|
39
|
+
if [[ "$TEST" == "test/system"* ]] ; then
|
40
|
+
bundle exec rake test:system
|
41
|
+
else
|
42
|
+
bundle exec rake test
|
43
|
+
fi
|
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.
|
4
|
+
version: 1.0.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-
|
11
|
+
date: 2025-02-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sin_lru_redux
|
@@ -68,21 +68,23 @@ files:
|
|
68
68
|
- README.md
|
69
69
|
- Rakefile
|
70
70
|
- lib/tailwind_merge.rb
|
71
|
-
- lib/tailwind_merge/
|
71
|
+
- lib/tailwind_merge/class_group_utils.rb
|
72
72
|
- lib/tailwind_merge/config.rb
|
73
|
-
- lib/tailwind_merge/
|
73
|
+
- lib/tailwind_merge/parse_class_name.rb
|
74
|
+
- lib/tailwind_merge/sort_modifiers.rb
|
74
75
|
- lib/tailwind_merge/validators.rb
|
75
76
|
- lib/tailwind_merge/version.rb
|
77
|
+
- script/test
|
76
78
|
- tailwind_merge.gemspec
|
77
|
-
homepage: https://github.com/gjtorikian/tailwind_merge/tree/
|
79
|
+
homepage: https://github.com/gjtorikian/tailwind_merge/tree/v1.0.0
|
78
80
|
licenses:
|
79
81
|
- MIT
|
80
82
|
metadata:
|
81
|
-
homepage_uri: https://github.com/gjtorikian/tailwind_merge/tree/
|
82
|
-
source_code_uri: https://github.com/gjtorikian/tailwind_merge/tree/
|
83
|
-
changelog_uri: https://github.com/gjtorikian/tailwind_merge/blob/
|
83
|
+
homepage_uri: https://github.com/gjtorikian/tailwind_merge/tree/v1.0.0
|
84
|
+
source_code_uri: https://github.com/gjtorikian/tailwind_merge/tree/v1.0.0
|
85
|
+
changelog_uri: https://github.com/gjtorikian/tailwind_merge/blob/v1.0.0/CHANGELOG.md
|
84
86
|
bug_tracker_uri: https://github.com/gjtorikian/tailwind_merge/issues
|
85
|
-
documentation_uri: https://rubydoc.info/gems/tailwind_merge/0.
|
87
|
+
documentation_uri: https://rubydoc.info/gems/tailwind_merge/1.0.0
|
86
88
|
funding_uri: https://github.com/sponsors/gjtorikian
|
87
89
|
rubygems_mfa_required: 'true'
|
88
90
|
post_install_message:
|
@@ -1,68 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module TailwindMerge
|
4
|
-
module ModifierUtils
|
5
|
-
IMPORTANT_MODIFIER = "!"
|
6
|
-
|
7
|
-
def split_modifiers(class_name, separator: ":")
|
8
|
-
separator_length = separator.length
|
9
|
-
separator_is_single_char = (separator_length == 1)
|
10
|
-
first_separator_char = separator[0]
|
11
|
-
|
12
|
-
modifiers = []
|
13
|
-
bracket_depth = 0
|
14
|
-
modifier_start = 0
|
15
|
-
postfix_modifier_position = nil
|
16
|
-
|
17
|
-
class_name.each_char.with_index do |char, index|
|
18
|
-
if bracket_depth.zero?
|
19
|
-
if char == first_separator_char && (separator_is_single_char || class_name[index, separator_length] == separator)
|
20
|
-
modifiers << class_name[modifier_start...index]
|
21
|
-
modifier_start = index + separator_length
|
22
|
-
next
|
23
|
-
elsif char == "/"
|
24
|
-
postfix_modifier_position = index
|
25
|
-
next
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
bracket_depth += 1 if char == "["
|
30
|
-
bracket_depth -= 1 if char == "]"
|
31
|
-
end
|
32
|
-
|
33
|
-
base_class_name_with_important_modifier = modifiers.empty? ? class_name : class_name[modifier_start..]
|
34
|
-
has_important_modifier = base_class_name_with_important_modifier.start_with?(IMPORTANT_MODIFIER)
|
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
|
40
|
-
|
41
|
-
[modifiers, has_important_modifier, base_class_name, maybe_postfix_modifier_position]
|
42
|
-
end
|
43
|
-
|
44
|
-
# Sorts modifiers according to following schema:
|
45
|
-
# - Predefined modifiers are sorted alphabetically
|
46
|
-
# - When an arbitrary variant appears, it must be preserved which modifiers are before and after it
|
47
|
-
def sort_modifiers(modifiers)
|
48
|
-
return modifiers if modifiers.size <= 1
|
49
|
-
|
50
|
-
sorted_modifiers = []
|
51
|
-
unsorted_modifiers = []
|
52
|
-
|
53
|
-
modifiers.each do |modifier|
|
54
|
-
if modifier.start_with?("[")
|
55
|
-
sorted_modifiers.concat(unsorted_modifiers.sort)
|
56
|
-
sorted_modifiers << modifier
|
57
|
-
unsorted_modifiers.clear
|
58
|
-
else
|
59
|
-
unsorted_modifiers << modifier
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
sorted_modifiers.concat(unsorted_modifiers.sort)
|
64
|
-
|
65
|
-
sorted_modifiers
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|