clsx-ruby 1.1.3 → 1.2.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 +10 -0
- data/README.md +75 -15
- data/lib/clsx/helper.rb +67 -29
- data/lib/clsx/tailwind_merge.rb +66 -0
- data/lib/clsx/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 102bb836f576baf8a09d29ce9eacbcfe3023ce9ba76d0099087d1978c4a6f451
|
|
4
|
+
data.tar.gz: 7d25f7931b2afad81609037e54ee004ffb02197aee6300547d8904249125a6c7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 574f8ee8bc63b7d4dbeaaae07a8232ea8fc0d0459f68d60eb99b2191c35060bc200530898c940f0b6f7f91753faab898b41813227347bc8108f2b599d8eb46ed
|
|
7
|
+
data.tar.gz: d5da4ba0eef261a1e19bfa83976c83e05ad36803538dccf1a5a090c7a54516c05dbda502948aa5b52f1c8f12deedf23e08bb2b26dafb852e81827233c9a99201
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## v1.2.0
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Optional Tailwind class merging via the `tailwind_merge` gem: `require 'clsx/tailwind_merge'` adds `twm`/`Twm[]` (and `Clsx.twm`) that resolve conflicting utilities (e.g. `px-2 px-4` → `px-4`). `clsx`/`cn` stay pure; the core gem stays zero-dependency unless the integration is required. Configure with `Clsx.merger = TailwindMerge::Merger.new(config: {...})`.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Faster `clsx`/`cn` for the common single-string and hash inputs via dispatch elision, fewer allocations, and cheaper whitespace scanning (no API or output change)
|
|
19
|
+
|
|
10
20
|
## v1.1.3
|
|
11
21
|
|
|
12
22
|
### Fixed
|
data/README.md
CHANGED
|
@@ -34,18 +34,19 @@ For Rails integration (adds `clsx` and `cn` helpers to all views), see [clsx-rai
|
|
|
34
34
|
|
|
35
35
|
### Blazing fast
|
|
36
36
|
|
|
37
|
-
**2
|
|
37
|
+
**2–4x faster** than Rails `class_names` — never slower, on realistic markup:
|
|
38
38
|
|
|
39
39
|
| Scenario | clsx-ruby | Rails `class_names` | Speedup |
|
|
40
40
|
|---|---|---|---|
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
|
|
|
41
|
+
| Token | 4.7M i/s | 1.1M i/s | **4.2x** |
|
|
42
|
+
| String array | 1.3M i/s | 452K i/s | **3.0x** |
|
|
43
|
+
| String + hash | 1.8M i/s | 610K i/s | **2.9x** |
|
|
44
|
+
| Utility string | 1.0M i/s | 371K i/s | **2.7x** |
|
|
45
|
+
| Long utility | 561K i/s | 209K i/s | **2.7x** |
|
|
46
|
+
| Utility + hash | 1.1M i/s | 457K i/s | **2.3x** |
|
|
47
|
+
| Hash | 1.9M i/s | 936K i/s | **2.0x** |
|
|
47
48
|
|
|
48
|
-
<sup>Ruby 4.0.
|
|
49
|
+
<sup>Ruby 4.0.5, Apple M1 Pro. 2.8× geomean; each row verified to produce output identical to Rails `class_names`. Reproduce: `bundle exec ruby benchmark/vs_rails.rb`</sup>
|
|
49
50
|
|
|
50
51
|
### More feature-rich than `class_names`
|
|
51
52
|
|
|
@@ -53,7 +54,7 @@ For Rails integration (adds `clsx` and `cn` helpers to all views), see [clsx-rai
|
|
|
53
54
|
|---|---|---|
|
|
54
55
|
| Conditional classes | ✅ | ✅ |
|
|
55
56
|
| Auto-deduplication | ✅ | ✅ |
|
|
56
|
-
| 2–
|
|
57
|
+
| 2–4× faster | ✅ | ❌ |
|
|
57
58
|
| Returns `nil` when empty | ✅ | ❌ (returns `""`) |
|
|
58
59
|
| Complex hash keys | ✅ | ❌ |
|
|
59
60
|
| Framework-agnostic | ✅ | ❌ |
|
|
@@ -149,6 +150,11 @@ Clsx['foo', ['bar', { baz: false, bat: nil }, ['hello', ['world']]], 'cya']
|
|
|
149
150
|
|
|
150
151
|
## Framework Examples
|
|
151
152
|
|
|
153
|
+
clsx is framework-agnostic. Any Ruby view object — ViewComponent, Phlex, or a plain object —
|
|
154
|
+
gets `clsx`/`cn` by including `Clsx::Helper`; no adapter needed. For Rails ERB views, the
|
|
155
|
+
companion [clsx-rails](https://github.com/svyatov/clsx-rails) gem auto-loads the helpers into
|
|
156
|
+
ActionView.
|
|
157
|
+
|
|
152
158
|
### Rails
|
|
153
159
|
|
|
154
160
|
```erb
|
|
@@ -165,17 +171,27 @@ erb :"<div class='#{Clsx['nav', active: @active]}'>...</div>"
|
|
|
165
171
|
|
|
166
172
|
### ViewComponent
|
|
167
173
|
|
|
174
|
+
Include the mixin once in your base component instead of per component:
|
|
175
|
+
|
|
168
176
|
```ruby
|
|
169
|
-
class
|
|
177
|
+
class ApplicationComponent < ViewComponent::Base
|
|
170
178
|
include Clsx::Helper
|
|
179
|
+
end
|
|
180
|
+
```
|
|
171
181
|
|
|
172
|
-
|
|
182
|
+
Accept a caller-supplied `class:` and merge it — clsx dedupes across every argument, so
|
|
183
|
+
callers can extend or repeat classes safely:
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
class AlertComponent < ApplicationComponent
|
|
187
|
+
def initialize(variant: :info, dismissible: false, class: nil)
|
|
173
188
|
@variant = variant
|
|
174
189
|
@dismissible = dismissible
|
|
190
|
+
@html_class = binding.local_variable_get(:class) # `class` is a Ruby keyword
|
|
175
191
|
end
|
|
176
192
|
|
|
177
193
|
def classes
|
|
178
|
-
clsx("alert", "alert-#{@variant}", dismissible: @dismissible)
|
|
194
|
+
clsx("alert", "alert-#{@variant}", @html_class, dismissible: @dismissible)
|
|
179
195
|
end
|
|
180
196
|
end
|
|
181
197
|
```
|
|
@@ -204,23 +220,67 @@ class NavLink < ViewComponent::Base
|
|
|
204
220
|
end
|
|
205
221
|
```
|
|
206
222
|
|
|
223
|
+
#### Merging conflicting utilities
|
|
224
|
+
|
|
225
|
+
`clsx`/`cn` keep every class, so conflicting Tailwind utilities both survive:
|
|
226
|
+
|
|
227
|
+
```ruby
|
|
228
|
+
Clsx['px-2 px-4'] # => "px-2 px-4"
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
For conflict resolution, opt into the [`tailwind_merge`](https://github.com/gjtorikian/tailwind_merge)
|
|
232
|
+
gem. Add it to your `Gemfile` (clsx-ruby itself stays dependency-free), then require the
|
|
233
|
+
integration once at boot:
|
|
234
|
+
|
|
235
|
+
```ruby
|
|
236
|
+
# config/initializers/clsx.rb
|
|
237
|
+
require 'clsx/tailwind_merge'
|
|
238
|
+
|
|
239
|
+
# Optional: configure the merger (prefix, cache size, custom theme, …)
|
|
240
|
+
Clsx.merger = TailwindMerge::Merger.new(config: { prefix: 'tw' })
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
This adds a merged variant — `twm` / `Twm[]` — the last conflicting utility wins.
|
|
244
|
+
`clsx`/`cn` stay pure; only `twm`/`Twm` merge:
|
|
245
|
+
|
|
246
|
+
```ruby
|
|
247
|
+
Twm['px-2 px-4'] # => "px-4"
|
|
248
|
+
Twm['p-4', 'p-2', 'bg-red', 'bg-blue'] # => "p-2 bg-blue"
|
|
249
|
+
Clsx['px-2 px-4'] # => "px-2 px-4" (unchanged)
|
|
250
|
+
|
|
251
|
+
# Also available as a mixin method and a module method:
|
|
252
|
+
include Clsx::Helper
|
|
253
|
+
twm('px-2 px-4') # => "px-4"
|
|
254
|
+
Clsx.twm('px-2 px-4') # => "px-4"
|
|
255
|
+
```
|
|
256
|
+
|
|
207
257
|
### Phlex
|
|
208
258
|
|
|
259
|
+
Include the mixin once in your base component, then merge caller-supplied attributes —
|
|
260
|
+
clsx dedupes across every argument:
|
|
261
|
+
|
|
209
262
|
```ruby
|
|
210
|
-
class
|
|
263
|
+
class ApplicationComponent < Phlex::HTML
|
|
211
264
|
include Clsx::Helper
|
|
265
|
+
end
|
|
212
266
|
|
|
213
|
-
|
|
267
|
+
class Badge < ApplicationComponent
|
|
268
|
+
def initialize(color: :blue, pill: false, **attributes)
|
|
214
269
|
@color = color
|
|
215
270
|
@pill = pill
|
|
271
|
+
@attributes = attributes
|
|
216
272
|
end
|
|
217
273
|
|
|
218
274
|
def view_template
|
|
219
|
-
span(class: clsx("badge", "badge-#{@color}", pill: @pill)) { yield }
|
|
275
|
+
span(class: clsx("badge", "badge-#{@color}", @attributes[:class], pill: @pill)) { yield }
|
|
220
276
|
end
|
|
221
277
|
end
|
|
222
278
|
```
|
|
223
279
|
|
|
280
|
+
Phlex's own `class: [...]` arrays and `mix` cover simple cases. Reach for clsx when you want
|
|
281
|
+
hash-conditional syntax (`pill: @pill`) or cross-argument dedup when merging caller-supplied
|
|
282
|
+
classes.
|
|
283
|
+
|
|
224
284
|
## Differences from JavaScript clsx
|
|
225
285
|
|
|
226
286
|
1. **Returns `nil`** when no classes apply (not an empty string). This prevents rendering empty `class=""` attributes in template engines that skip `nil`:
|
data/lib/clsx/helper.rb
CHANGED
|
@@ -7,6 +7,11 @@ module Clsx
|
|
|
7
7
|
# include Clsx::Helper
|
|
8
8
|
# clsx('btn', active: @active) # => "btn active"
|
|
9
9
|
module Helper
|
|
10
|
+
# Single-pass scan for tab/newline. Collapses two consecutive `include?`
|
|
11
|
+
# scans into one regex match (no MatchData alloc). Space is checked
|
|
12
|
+
# separately first so the common "has a space" case still short-circuits.
|
|
13
|
+
TAB_OR_NEWLINE = /[\t\n]/
|
|
14
|
+
|
|
10
15
|
# Build a CSS class string from an arbitrary mix of arguments.
|
|
11
16
|
#
|
|
12
17
|
# Falsy values (+nil+, +false+) and standalone +true+ are discarded.
|
|
@@ -26,12 +31,29 @@ module Clsx
|
|
|
26
31
|
# clsx(%w[foo bar], hidden: true) # => "foo bar hidden"
|
|
27
32
|
def clsx(*args)
|
|
28
33
|
return nil if args.empty?
|
|
29
|
-
|
|
34
|
+
|
|
35
|
+
if args.size == 1
|
|
36
|
+
arg = args[0]
|
|
37
|
+
# Inline the two dominant single-arg shapes (String, Hash) so they skip
|
|
38
|
+
# the clsx_one dispatch. Other types fall through to clsx_one, which no
|
|
39
|
+
# longer re-checks String or Hash (already ruled out here).
|
|
40
|
+
if arg.is_a?(String)
|
|
41
|
+
return nil if arg.empty?
|
|
42
|
+
return arg unless arg.include?(' ') || arg.match?(TAB_OR_NEWLINE)
|
|
43
|
+
|
|
44
|
+
return clsx_dedup_str(arg)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
return clsx_hash(arg) if arg.is_a?(Hash)
|
|
48
|
+
|
|
49
|
+
return clsx_one(arg)
|
|
50
|
+
end
|
|
30
51
|
|
|
31
52
|
if args.size == 2 && args[0].is_a?(String) && args[1].is_a?(Hash)
|
|
32
53
|
str = args[0]
|
|
33
54
|
return clsx_hash(args[1]) if str.empty?
|
|
34
|
-
return clsx_str_hash_full(str, args[1]) if str.include?(' ') || str.
|
|
55
|
+
return clsx_str_hash_full(str, args[1]) if str.include?(' ') || str.match?(TAB_OR_NEWLINE)
|
|
56
|
+
|
|
35
57
|
return clsx_str_hash(str, args[1])
|
|
36
58
|
end
|
|
37
59
|
|
|
@@ -45,31 +67,19 @@ module Clsx
|
|
|
45
67
|
|
|
46
68
|
private
|
|
47
69
|
|
|
48
|
-
# Single-argument fast path
|
|
49
|
-
#
|
|
70
|
+
# Single-argument fast path for descriptors other than String and Hash,
|
|
71
|
+
# both of which are handled inline in {#clsx} — this method never sees them.
|
|
50
72
|
#
|
|
51
|
-
# @param arg [Object] single class descriptor
|
|
73
|
+
# @param arg [Object] single non-String, non-Hash class descriptor
|
|
52
74
|
# @return [String, nil]
|
|
53
75
|
def clsx_one(arg)
|
|
54
|
-
if arg.is_a?(String)
|
|
55
|
-
return nil if arg.empty?
|
|
56
|
-
return arg unless arg.include?(' ') || arg.include?("\t") || arg.include?("\n")
|
|
57
|
-
|
|
58
|
-
parts = arg.split
|
|
59
|
-
return nil if parts.empty?
|
|
60
|
-
return parts[0] if parts.length == 1
|
|
61
|
-
return arg if !parts.uniq! && parts.length == arg.count(' ') + 1 # rubocop:disable Layout/EmptyLineAfterGuardClause
|
|
62
|
-
return parts.join(' ')
|
|
63
|
-
end
|
|
64
|
-
|
|
65
76
|
if arg.is_a?(Symbol)
|
|
66
77
|
s = arg.name
|
|
67
|
-
return s unless s.include?(' ') || s.
|
|
78
|
+
return s unless s.include?(' ') || s.match?(TAB_OR_NEWLINE)
|
|
79
|
+
|
|
68
80
|
return clsx_dedup_str(s)
|
|
69
81
|
end
|
|
70
82
|
|
|
71
|
-
return clsx_hash(arg) if arg.is_a?(Hash)
|
|
72
|
-
|
|
73
83
|
if arg.is_a?(Array)
|
|
74
84
|
return nil if arg.empty?
|
|
75
85
|
|
|
@@ -93,13 +103,20 @@ module Clsx
|
|
|
93
103
|
parts = str.split
|
|
94
104
|
return nil if parts.empty?
|
|
95
105
|
return parts[0] if parts.length == 1
|
|
106
|
+
# Already canonical (single-spaced, no dup/leading/trailing/tab/newline)
|
|
107
|
+
# iff uniq! removed nothing and token count == space count + 1; then
|
|
108
|
+
# return str as-is and skip the join allocation.
|
|
96
109
|
return str if !parts.uniq! && parts.length == str.count(' ') + 1
|
|
97
110
|
|
|
98
111
|
parts.join(' ')
|
|
99
112
|
end
|
|
100
113
|
|
|
101
|
-
# Hash-only fast path using string buffer
|
|
102
|
-
#
|
|
114
|
+
# Hash-only fast path using a string buffer, skipping cross-key dedup.
|
|
115
|
+
# Hash keys are unique, so the only way two distinct keys collide on a
|
|
116
|
+
# class name is a Symbol and a String of the same name (+:foo+ and
|
|
117
|
+
# +"foo"+ both yield "foo"). Hence on mixed key types — plus multi-token
|
|
118
|
+
# or complex (Array/Hash) keys — it falls back to {#clsx_hash_full}, which
|
|
119
|
+
# deduplicates.
|
|
103
120
|
#
|
|
104
121
|
# @param hash [Hash] class-name => condition pairs
|
|
105
122
|
# @return [String, nil]
|
|
@@ -107,6 +124,7 @@ module Clsx
|
|
|
107
124
|
return nil if hash.empty?
|
|
108
125
|
|
|
109
126
|
buf = nil
|
|
127
|
+
owned = false
|
|
110
128
|
key_type = nil
|
|
111
129
|
|
|
112
130
|
hash.each do |key, value|
|
|
@@ -119,7 +137,15 @@ module Clsx
|
|
|
119
137
|
s = key.name
|
|
120
138
|
return clsx_hash_full(hash) if s.include?(' ')
|
|
121
139
|
|
|
122
|
-
|
|
140
|
+
# Defer the dup: hold the first token by reference (frozen name or the
|
|
141
|
+
# caller's key) and only copy when a second token must be appended.
|
|
142
|
+
if buf
|
|
143
|
+
buf = buf.dup unless owned
|
|
144
|
+
owned = true
|
|
145
|
+
buf << ' ' << s
|
|
146
|
+
else
|
|
147
|
+
buf = s
|
|
148
|
+
end
|
|
123
149
|
elsif key.is_a?(String)
|
|
124
150
|
next if key.empty?
|
|
125
151
|
|
|
@@ -127,14 +153,20 @@ module Clsx
|
|
|
127
153
|
return clsx_hash_full(hash) if key.include?(' ')
|
|
128
154
|
|
|
129
155
|
key_type = :string
|
|
130
|
-
|
|
156
|
+
if buf
|
|
157
|
+
buf = buf.dup unless owned
|
|
158
|
+
owned = true
|
|
159
|
+
buf << ' ' << key
|
|
160
|
+
else
|
|
161
|
+
buf = key
|
|
162
|
+
end
|
|
131
163
|
else
|
|
132
164
|
return clsx_hash_full(hash)
|
|
133
165
|
end
|
|
134
166
|
end
|
|
135
167
|
|
|
136
168
|
return nil unless buf
|
|
137
|
-
return clsx_dedup_str(buf) if buf.
|
|
169
|
+
return clsx_dedup_str(buf) if buf.match?(TAB_OR_NEWLINE)
|
|
138
170
|
|
|
139
171
|
buf
|
|
140
172
|
end
|
|
@@ -150,7 +182,13 @@ module Clsx
|
|
|
150
182
|
end
|
|
151
183
|
|
|
152
184
|
# Fast path for +clsx('base', active: cond)+ pattern where base is a
|
|
153
|
-
# single token. Deduplicates via direct string comparison
|
|
185
|
+
# single token. Deduplicates via direct string comparison against +str+.
|
|
186
|
+
#
|
|
187
|
+
# A key containing a space forces the {#clsx_str_hash_full} fallback —
|
|
188
|
+
# whole-key comparison can't dedup tokens *inside* a multi-token key, and
|
|
189
|
+
# the trailing normalization only rescans for tab/newline. Tab/newline keys
|
|
190
|
+
# are kept and normalized by that trailing {#clsx_dedup_str} pass, which
|
|
191
|
+
# splits on all whitespace.
|
|
154
192
|
#
|
|
155
193
|
# @param str [String] base class name
|
|
156
194
|
# @param hash [Hash] class-name => condition pairs
|
|
@@ -188,7 +226,7 @@ module Clsx
|
|
|
188
226
|
end
|
|
189
227
|
end
|
|
190
228
|
|
|
191
|
-
return clsx_dedup_str(buf) if buf.
|
|
229
|
+
return clsx_dedup_str(buf) if buf.match?(TAB_OR_NEWLINE)
|
|
192
230
|
|
|
193
231
|
buf
|
|
194
232
|
end
|
|
@@ -221,7 +259,7 @@ module Clsx
|
|
|
221
259
|
|
|
222
260
|
key_type = :symbol
|
|
223
261
|
s = key.name
|
|
224
|
-
return clsx_str_hash_full_walk(parts, hash) if s.include?(' ') || s.
|
|
262
|
+
return clsx_str_hash_full_walk(parts, hash) if s.include?(' ') || s.match?(TAB_OR_NEWLINE)
|
|
225
263
|
|
|
226
264
|
next if parts.include?(s)
|
|
227
265
|
|
|
@@ -231,7 +269,7 @@ module Clsx
|
|
|
231
269
|
|
|
232
270
|
key_type = :string
|
|
233
271
|
next if key.empty?
|
|
234
|
-
return clsx_str_hash_full_walk(parts, hash) if key.include?(' ') || key.
|
|
272
|
+
return clsx_str_hash_full_walk(parts, hash) if key.include?(' ') || key.match?(TAB_OR_NEWLINE)
|
|
235
273
|
|
|
236
274
|
next if parts.include?(key)
|
|
237
275
|
|
|
@@ -318,7 +356,7 @@ module Clsx
|
|
|
318
356
|
return nil if seen.empty?
|
|
319
357
|
|
|
320
358
|
result = seen.keys.join(' ')
|
|
321
|
-
return result if result.count(' ') + 1 == seen.size && !result.
|
|
359
|
+
return result if result.count(' ') + 1 == seen.size && !result.match?(TAB_OR_NEWLINE)
|
|
322
360
|
|
|
323
361
|
normalized = result.split.uniq.join(' ')
|
|
324
362
|
normalized.empty? ? nil : normalized
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'clsx'
|
|
4
|
+
require 'tailwind_merge'
|
|
5
|
+
|
|
6
|
+
# Opt-in Tailwind merging. Requiring this file (once, at boot) pulls in the
|
|
7
|
+
# {https://github.com/gjtorikian/tailwind_merge tailwind_merge} gem and adds a
|
|
8
|
+
# merged variant of +clsx+ — {Clsx.twm}, the {Helper#twm} mixin method, and the
|
|
9
|
+
# +Twm[]+ shortcut — that resolves conflicting Tailwind utilities
|
|
10
|
+
# (e.g. +"px-2 px-4"+ becomes +"px-4"+).
|
|
11
|
+
#
|
|
12
|
+
# +clsx+/+cn+ are left untouched and stay pure; only +twm+/+Twm+ merge. Without
|
|
13
|
+
# requiring this file the core gem carries no dependency.
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# require 'clsx/tailwind_merge'
|
|
17
|
+
# Twm['px-2 px-4'] # => "px-4"
|
|
18
|
+
#
|
|
19
|
+
# @example Custom merger (configure once at boot)
|
|
20
|
+
# Clsx.merger = TailwindMerge::Merger.new(config: { prefix: 'tw' })
|
|
21
|
+
module Clsx
|
|
22
|
+
@merger_mutex = Mutex.new
|
|
23
|
+
|
|
24
|
+
class << self
|
|
25
|
+
# @param merger [#merge] a preconfigured merger (or +nil+ to reset to the lazy default)
|
|
26
|
+
# @return [#merge, nil]
|
|
27
|
+
attr_writer :merger
|
|
28
|
+
|
|
29
|
+
# Process-wide merger, built once. Building a {TailwindMerge::Merger} is
|
|
30
|
+
# expensive, so double-checked locking constructs it exactly once even under
|
|
31
|
+
# concurrent first use; after that the lock is never taken again.
|
|
32
|
+
#
|
|
33
|
+
# @return [#merge]
|
|
34
|
+
def merger
|
|
35
|
+
@merger || @merger_mutex.synchronize { @merger ||= TailwindMerge::Merger.new }
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Adds the merged {#twm} variant to the {Helper} mixin.
|
|
40
|
+
module Helper
|
|
41
|
+
# Like {#clsx}, but pipes the result through {Clsx.merger} to resolve
|
|
42
|
+
# conflicting Tailwind utilities. Returns +nil+ (skipping the merger) when no
|
|
43
|
+
# classes apply, matching {#clsx}.
|
|
44
|
+
#
|
|
45
|
+
# @return [String, nil]
|
|
46
|
+
#
|
|
47
|
+
# @example
|
|
48
|
+
# twm('px-2 px-4') # => "px-4"
|
|
49
|
+
# twm('px-2', 'px-4', hidden: c) # => "px-4"
|
|
50
|
+
def twm(*)
|
|
51
|
+
classes = clsx(*)
|
|
52
|
+
classes && Clsx.merger.merge(classes)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Bracket shortcut for {Helper#twm}, mirroring {Clsx.[]}.
|
|
57
|
+
module Twm
|
|
58
|
+
# (see Helper#twm)
|
|
59
|
+
def self.[](*)
|
|
60
|
+
Clsx.twm(*)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Short top-level shortcut — only defined if +Twm+ is not already taken.
|
|
66
|
+
Twm = Clsx::Twm unless Object.const_defined?(:Twm)
|
data/lib/clsx/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: clsx-ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Leonid Svyatov
|
|
@@ -23,6 +23,7 @@ files:
|
|
|
23
23
|
- README.md
|
|
24
24
|
- lib/clsx.rb
|
|
25
25
|
- lib/clsx/helper.rb
|
|
26
|
+
- lib/clsx/tailwind_merge.rb
|
|
26
27
|
- lib/clsx/version.rb
|
|
27
28
|
homepage: https://github.com/svyatov/clsx-ruby
|
|
28
29
|
licenses:
|
|
@@ -45,7 +46,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
45
46
|
- !ruby/object:Gem::Version
|
|
46
47
|
version: '0'
|
|
47
48
|
requirements: []
|
|
48
|
-
rubygems_version: 4.0.
|
|
49
|
+
rubygems_version: 4.0.12
|
|
49
50
|
specification_version: 4
|
|
50
51
|
summary: The fastest conditional CSS class builder for Ruby
|
|
51
52
|
test_files: []
|