daisyui 1.3.1 → 2.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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -0
  3. data/LICENSE-tailwind_merge.txt +21 -0
  4. data/README.md +58 -0
  5. data/lib/daisy_ui/accordion.rb +32 -28
  6. data/lib/daisy_ui/alert.rb +97 -94
  7. data/lib/daisy_ui/aura.rb +37 -37
  8. data/lib/daisy_ui/avatar.rb +16 -14
  9. data/lib/daisy_ui/badge.rb +159 -156
  10. data/lib/daisy_ui/base.rb +38 -4
  11. data/lib/daisy_ui/button.rb +148 -142
  12. data/lib/daisy_ui/card.rb +57 -54
  13. data/lib/daisy_ui/carousel.rb +36 -32
  14. data/lib/daisy_ui/chat.rb +16 -15
  15. data/lib/daisy_ui/checkbox.rb +95 -93
  16. data/lib/daisy_ui/class_merge/cache.rb +40 -0
  17. data/lib/daisy_ui/class_merge/class_group_utils.rb +139 -0
  18. data/lib/daisy_ui/class_merge/configuration.rb +77 -0
  19. data/lib/daisy_ui/class_merge/daisy_groups.rb +83 -0
  20. data/lib/daisy_ui/class_merge/merger.rb +126 -0
  21. data/lib/daisy_ui/class_merge/parse_class_name.rb +110 -0
  22. data/lib/daisy_ui/class_merge/sort_modifiers.rb +36 -0
  23. data/lib/daisy_ui/class_merge/tailwind_config.rb +2505 -0
  24. data/lib/daisy_ui/class_merge/validators.rb +202 -0
  25. data/lib/daisy_ui/class_merge.rb +47 -0
  26. data/lib/daisy_ui/collapse.rb +32 -28
  27. data/lib/daisy_ui/configurable.rb +31 -3
  28. data/lib/daisy_ui/divider.rb +84 -78
  29. data/lib/daisy_ui/dock.rb +37 -35
  30. data/lib/daisy_ui/dropdown.rb +67 -65
  31. data/lib/daisy_ui/file_input.rb +95 -91
  32. data/lib/daisy_ui/footer.rb +16 -14
  33. data/lib/daisy_ui/indicator.rb +40 -36
  34. data/lib/daisy_ui/input.rb +95 -91
  35. data/lib/daisy_ui/join.rb +16 -14
  36. data/lib/daisy_ui/kbd.rb +37 -35
  37. data/lib/daisy_ui/link.rb +58 -58
  38. data/lib/daisy_ui/loading.rb +85 -81
  39. data/lib/daisy_ui/mask.rb +123 -119
  40. data/lib/daisy_ui/megamenu.rb +53 -50
  41. data/lib/daisy_ui/menu.rb +57 -53
  42. data/lib/daisy_ui/modal.rb +49 -47
  43. data/lib/daisy_ui/otp.rb +95 -93
  44. data/lib/daisy_ui/pagination.rb +16 -14
  45. data/lib/daisy_ui/progress.rb +58 -56
  46. data/lib/daisy_ui/radio.rb +95 -91
  47. data/lib/daisy_ui/range.rb +95 -91
  48. data/lib/daisy_ui/rating.rb +37 -35
  49. data/lib/daisy_ui/select.rb +95 -91
  50. data/lib/daisy_ui/stack.rb +24 -22
  51. data/lib/daisy_ui/stat.rb +16 -14
  52. data/lib/daisy_ui/status.rb +95 -91
  53. data/lib/daisy_ui/steps.rb +16 -14
  54. data/lib/daisy_ui/swap.rb +16 -14
  55. data/lib/daisy_ui/table.rb +41 -39
  56. data/lib/daisy_ui/tabs.rb +80 -77
  57. data/lib/daisy_ui/textarea.rb +95 -91
  58. data/lib/daisy_ui/timeline.rb +16 -14
  59. data/lib/daisy_ui/toast.rb +46 -42
  60. data/lib/daisy_ui/toggle.rb +95 -91
  61. data/lib/daisy_ui/tooltip.rb +111 -105
  62. data/lib/daisy_ui/updated_at.rb +1 -1
  63. data/lib/daisy_ui/version.rb +1 -1
  64. metadata +18 -1
@@ -30,99 +30,101 @@ module DaisyUI
30
30
  end
31
31
 
32
32
  register_modifiers(
33
- # Colors
34
- # "sm:checkbox-neutral"
35
- # "@sm:checkbox-neutral"
36
- # "md:checkbox-neutral"
37
- # "@md:checkbox-neutral"
38
- # "lg:checkbox-neutral"
39
- # "@lg:checkbox-neutral"
40
- neutral: "checkbox-neutral",
41
- # "sm:checkbox-primary"
42
- # "@sm:checkbox-primary"
43
- # "md:checkbox-primary"
44
- # "@md:checkbox-primary"
45
- # "lg:checkbox-primary"
46
- # "@lg:checkbox-primary"
47
- primary: "checkbox-primary",
48
- # "sm:checkbox-secondary"
49
- # "@sm:checkbox-secondary"
50
- # "md:checkbox-secondary"
51
- # "@md:checkbox-secondary"
52
- # "lg:checkbox-secondary"
53
- # "@lg:checkbox-secondary"
54
- secondary: "checkbox-secondary",
55
- # "sm:checkbox-accent"
56
- # "@sm:checkbox-accent"
57
- # "md:checkbox-accent"
58
- # "@md:checkbox-accent"
59
- # "lg:checkbox-accent"
60
- # "@lg:checkbox-accent"
61
- accent: "checkbox-accent",
62
- # "sm:checkbox-success"
63
- # "@sm:checkbox-success"
64
- # "md:checkbox-success"
65
- # "@md:checkbox-success"
66
- # "lg:checkbox-success"
67
- # "@lg:checkbox-success"
68
- success: "checkbox-success",
69
- # "sm:checkbox-warning"
70
- # "@sm:checkbox-warning"
71
- # "md:checkbox-warning"
72
- # "@md:checkbox-warning"
73
- # "lg:checkbox-warning"
74
- # "@lg:checkbox-warning"
75
- warning: "checkbox-warning",
76
- # "sm:checkbox-info"
77
- # "@sm:checkbox-info"
78
- # "md:checkbox-info"
79
- # "@md:checkbox-info"
80
- # "lg:checkbox-info"
81
- # "@lg:checkbox-info"
82
- info: "checkbox-info",
83
- # "sm:checkbox-error"
84
- # "@sm:checkbox-error"
85
- # "md:checkbox-error"
86
- # "@md:checkbox-error"
87
- # "lg:checkbox-error"
88
- # "@lg:checkbox-error"
89
- error: "checkbox-error",
90
- # Sizes
91
- # "sm:checkbox-xs"
92
- # "@sm:checkbox-xs"
93
- # "md:checkbox-xs"
94
- # "@md:checkbox-xs"
95
- # "lg:checkbox-xs"
96
- # "@lg:checkbox-xs"
97
- xs: "checkbox-xs",
98
- # "sm:checkbox-sm"
99
- # "@sm:checkbox-sm"
100
- # "md:checkbox-sm"
101
- # "@md:checkbox-sm"
102
- # "lg:checkbox-sm"
103
- # "@lg:checkbox-sm"
104
- sm: "checkbox-sm",
105
- # "sm:checkbox-md"
106
- # "@sm:checkbox-md"
107
- # "md:checkbox-md"
108
- # "@md:checkbox-md"
109
- # "lg:checkbox-md"
110
- # "@lg:checkbox-md"
111
- md: "checkbox-md",
112
- # "sm:checkbox-lg"
113
- # "@sm:checkbox-lg"
114
- # "md:checkbox-lg"
115
- # "@md:checkbox-lg"
116
- # "lg:checkbox-lg"
117
- # "@lg:checkbox-lg"
118
- lg: "checkbox-lg",
119
- # "sm:checkbox-xl"
120
- # "@sm:checkbox-xl"
121
- # "md:checkbox-xl"
122
- # "@md:checkbox-xl"
123
- # "lg:checkbox-xl"
124
- # "@lg:checkbox-xl"
125
- xl: "checkbox-xl"
33
+ color: {
34
+ # "sm:checkbox-neutral"
35
+ # "@sm:checkbox-neutral"
36
+ # "md:checkbox-neutral"
37
+ # "@md:checkbox-neutral"
38
+ # "lg:checkbox-neutral"
39
+ # "@lg:checkbox-neutral"
40
+ neutral: "checkbox-neutral",
41
+ # "sm:checkbox-primary"
42
+ # "@sm:checkbox-primary"
43
+ # "md:checkbox-primary"
44
+ # "@md:checkbox-primary"
45
+ # "lg:checkbox-primary"
46
+ # "@lg:checkbox-primary"
47
+ primary: "checkbox-primary",
48
+ # "sm:checkbox-secondary"
49
+ # "@sm:checkbox-secondary"
50
+ # "md:checkbox-secondary"
51
+ # "@md:checkbox-secondary"
52
+ # "lg:checkbox-secondary"
53
+ # "@lg:checkbox-secondary"
54
+ secondary: "checkbox-secondary",
55
+ # "sm:checkbox-accent"
56
+ # "@sm:checkbox-accent"
57
+ # "md:checkbox-accent"
58
+ # "@md:checkbox-accent"
59
+ # "lg:checkbox-accent"
60
+ # "@lg:checkbox-accent"
61
+ accent: "checkbox-accent",
62
+ # "sm:checkbox-success"
63
+ # "@sm:checkbox-success"
64
+ # "md:checkbox-success"
65
+ # "@md:checkbox-success"
66
+ # "lg:checkbox-success"
67
+ # "@lg:checkbox-success"
68
+ success: "checkbox-success",
69
+ # "sm:checkbox-warning"
70
+ # "@sm:checkbox-warning"
71
+ # "md:checkbox-warning"
72
+ # "@md:checkbox-warning"
73
+ # "lg:checkbox-warning"
74
+ # "@lg:checkbox-warning"
75
+ warning: "checkbox-warning",
76
+ # "sm:checkbox-info"
77
+ # "@sm:checkbox-info"
78
+ # "md:checkbox-info"
79
+ # "@md:checkbox-info"
80
+ # "lg:checkbox-info"
81
+ # "@lg:checkbox-info"
82
+ info: "checkbox-info",
83
+ # "sm:checkbox-error"
84
+ # "@sm:checkbox-error"
85
+ # "md:checkbox-error"
86
+ # "@md:checkbox-error"
87
+ # "lg:checkbox-error"
88
+ # "@lg:checkbox-error"
89
+ error: "checkbox-error"
90
+ },
91
+ size: {
92
+ # "sm:checkbox-xs"
93
+ # "@sm:checkbox-xs"
94
+ # "md:checkbox-xs"
95
+ # "@md:checkbox-xs"
96
+ # "lg:checkbox-xs"
97
+ # "@lg:checkbox-xs"
98
+ xs: "checkbox-xs",
99
+ # "sm:checkbox-sm"
100
+ # "@sm:checkbox-sm"
101
+ # "md:checkbox-sm"
102
+ # "@md:checkbox-sm"
103
+ # "lg:checkbox-sm"
104
+ # "@lg:checkbox-sm"
105
+ sm: "checkbox-sm",
106
+ # "sm:checkbox-md"
107
+ # "@sm:checkbox-md"
108
+ # "md:checkbox-md"
109
+ # "@md:checkbox-md"
110
+ # "lg:checkbox-md"
111
+ # "@lg:checkbox-md"
112
+ md: "checkbox-md",
113
+ # "sm:checkbox-lg"
114
+ # "@sm:checkbox-lg"
115
+ # "md:checkbox-lg"
116
+ # "@md:checkbox-lg"
117
+ # "lg:checkbox-lg"
118
+ # "@lg:checkbox-lg"
119
+ lg: "checkbox-lg",
120
+ # "sm:checkbox-xl"
121
+ # "@sm:checkbox-xl"
122
+ # "md:checkbox-xl"
123
+ # "@md:checkbox-xl"
124
+ # "lg:checkbox-xl"
125
+ # "@lg:checkbox-xl"
126
+ xl: "checkbox-xl"
127
+ }
126
128
  )
127
129
  end
128
130
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "monitor"
4
+
5
+ module DaisyUI
6
+ module ClassMerge
7
+ # Thread-safe LRU cache. Ruby hashes keep insertion order, so re-inserting
8
+ # a key on every hit moves it to the end and the first key is always the
9
+ # least recently used one.
10
+ class Cache
11
+ def initialize(max_size)
12
+ @max_size = max_size
13
+ @data = {}
14
+ @monitor = Monitor.new
15
+ end
16
+
17
+ # Returns the cached value for key, or stores and returns the block's
18
+ # result. A nil result is not stored.
19
+ def getset(key)
20
+ @monitor.synchronize do
21
+ if @data.key?(key)
22
+ value = @data.delete(key)
23
+ @data[key] = value
24
+ return value
25
+ end
26
+
27
+ value = yield
28
+ return value if value.nil?
29
+
30
+ @data.shift if @data.size >= @max_size
31
+ @data[key] = value
32
+ end
33
+ end
34
+
35
+ def size
36
+ @monitor.synchronize { @data.size }
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Ported from tailwind_merge 1.5.6 (https://github.com/gjtorikian/tailwind_merge)
4
+ # Copyright (c) 2022 Garen J. Torikian. MIT License, see LICENSE-tailwind_merge.txt.
5
+
6
+ module DaisyUI
7
+ module ClassMerge
8
+ class ClassGroupUtils
9
+ attr_reader :class_map
10
+
11
+ CLASS_PART_SEPARATOR = "-"
12
+
13
+ ARBITRARY_PROPERTY_REGEX = /^\[(.+)\]$/
14
+
15
+ def initialize(config)
16
+ @config = config
17
+ @class_map = create_class_map(config)
18
+ end
19
+
20
+ def class_group_id(class_name)
21
+ class_parts = class_name.split(CLASS_PART_SEPARATOR)
22
+
23
+ # Classes like `-inset-1` produce an empty string as first class_part.
24
+ # Assume that classes for negative values are used correctly and remove it from class_parts.
25
+ class_parts.shift if class_parts.first == "" && class_parts.length != 1
26
+
27
+ get_group_recursive(class_parts, @class_map) || get_group_id_for_arbitrary_property(class_name)
28
+ end
29
+
30
+ def get_group_recursive(class_parts, class_part_object, index = 0)
31
+ return class_part_object[:class_group_id] if index == class_parts.length
32
+
33
+ current_class_part = class_parts[index]
34
+
35
+ next_class_part_object = class_part_object[:next_part][current_class_part]
36
+
37
+ if next_class_part_object
38
+ class_group_from_next_class_part = get_group_recursive(class_parts, next_class_part_object, index + 1)
39
+ return class_group_from_next_class_part if class_group_from_next_class_part
40
+ end
41
+
42
+ return if class_part_object[:validators].empty?
43
+
44
+ class_rest = index.zero? ? class_parts.join(CLASS_PART_SEPARATOR) : class_parts[index..].join(CLASS_PART_SEPARATOR)
45
+
46
+ # Theme procs are expanded at trie-build time (see process_classes_recursively),
47
+ # so validators here are always plain value validators.
48
+ result = class_part_object[:validators].find do |v|
49
+ v[:validator].call(class_rest)
50
+ end
51
+
52
+ result&.fetch(:class_group_id, nil)
53
+ end
54
+
55
+ def get_conflicting_class_group_ids(class_group_id, has_postfix_modifier)
56
+ conflicts = @config[:conflicting_class_groups][class_group_id] || []
57
+
58
+ return [*conflicts, *@config[:conflicting_class_group_modifiers][class_group_id]] if has_postfix_modifier && @config[:conflicting_class_group_modifiers][class_group_id]
59
+
60
+ conflicts
61
+ end
62
+
63
+ private
64
+
65
+ def create_class_map(config)
66
+ theme = config[:theme]
67
+ class_groups = config[:class_groups]
68
+ class_map = {
69
+ next_part: {},
70
+ validators: []
71
+ }
72
+
73
+ class_groups.each do |(class_group_id, class_group)|
74
+ process_classes_recursively(class_group, class_map, class_group_id, theme)
75
+ end
76
+
77
+ class_map
78
+ end
79
+
80
+ def process_classes_recursively(class_group, class_part_object, class_group_id, theme)
81
+ class_group.each do |class_definition|
82
+ if class_definition.is_a?(String)
83
+ class_part_object_to_edit = class_definition.empty? ? class_part_object : get_class_part(class_part_object, class_definition)
84
+ class_part_object_to_edit[:class_group_id] = class_group_id
85
+ elsif class_definition.is_a?(Proc)
86
+ if from_theme?(class_definition)
87
+ process_classes_recursively(class_definition.call(@config), class_part_object, class_group_id, theme)
88
+ else
89
+ class_part_object[:validators] << {
90
+ validator: class_definition,
91
+ class_group_id: class_group_id
92
+ }
93
+ end
94
+ else
95
+ class_definition.each do |key, nested_class_group|
96
+ process_classes_recursively(
97
+ nested_class_group,
98
+ get_class_part(class_part_object, key),
99
+ class_group_id,
100
+ theme
101
+ )
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ def get_class_part(class_part_object, path)
108
+ current_class_part_object = class_part_object
109
+
110
+ path.to_s.split(CLASS_PART_SEPARATOR).each do |path_part|
111
+ unless current_class_part_object[:next_part].key?(path_part)
112
+ current_class_part_object[:next_part][path_part] = {
113
+ next_part: {},
114
+ validators: []
115
+ }
116
+ end
117
+
118
+ current_class_part_object = current_class_part_object[:next_part][path_part]
119
+ end
120
+
121
+ current_class_part_object
122
+ end
123
+
124
+ def get_group_id_for_arbitrary_property(class_name)
125
+ match = ARBITRARY_PROPERTY_REGEX.match(class_name)
126
+ return unless match
127
+
128
+ property = match[1].to_s.split(":", 2).first
129
+
130
+ # Use two dots here because one dot is used as prefix for class groups in plugins
131
+ "arbitrary..#{property}" if property && !property.empty?
132
+ end
133
+
134
+ def from_theme?(validator)
135
+ TailwindConfig::VALID_THEME_IDS.include?(validator.object_id)
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DaisyUI
4
+ module ClassMerge
5
+ # Settings for DaisyUI::ClassMerge, reached through
6
+ # `DaisyUI.configure { |c| c.class_merge }`.
7
+ #
8
+ # DaisyUI.configure do |c|
9
+ # c.class_merge.enabled = true
10
+ # c.class_merge.utility("text-display", "text-hero", like: "text-lg")
11
+ # c.class_merge.theme(spacing: %w[gutter])
12
+ # end
13
+ #
14
+ # Custom colors (`bg-brand`, `text-brand`) need no registration.
15
+ class Configuration
16
+ THEME_KEYS = TailwindConfig::DEFAULTS[:theme].keys.freeze
17
+
18
+ attr_accessor :enabled
19
+
20
+ def initialize
21
+ @enabled = true
22
+ @theme = {}
23
+ @class_groups = {}
24
+ @conflicts = {}
25
+ end
26
+
27
+ # Registers utilities that behave like an existing one:
28
+ # `utility("text-display", like: "text-lg")` makes `text-display` a font
29
+ # size, so it replaces `text-sm` and sits next to `text-error`.
30
+ def utility(*names, like:)
31
+ group = ClassMerge.tailwind_utils.class_group_id(like) or
32
+ raise ArgumentError, "#{like.inspect} is not a known utility to model #{names.join(', ')} on"
33
+
34
+ class_groups(group => names.map(&:to_s))
35
+ end
36
+
37
+ # Appends values to Tailwind theme scales, which feed every utility using
38
+ # the scale: `spacing: %w[gutter]` covers `p-gutter`, `m-gutter`, `gap-gutter`.
39
+ def theme(**scales)
40
+ scales = scales.transform_keys { |key| key.to_s.tr("_", "-") }
41
+ unknown = scales.keys - THEME_KEYS
42
+ raise ArgumentError, "unknown theme key #{unknown.map(&:inspect).join(', ')}; use one of #{THEME_KEYS.join(', ')}" if unknown.any?
43
+
44
+ append(@theme, scales.transform_values { |values| Array(values).map(&:to_s) })
45
+ end
46
+
47
+ # Raw tailwind_merge class groups: `{ "group-id" => ["class", ...] }`.
48
+ # Classes in the same group conflict with each other.
49
+ def class_groups(groups)
50
+ append(@class_groups, groups)
51
+ end
52
+
53
+ # Raw tailwind_merge conflicts: `{ "group-id" => ["other-group-id", ...] }`
54
+ # removes earlier classes of the listed groups when a class of the key
55
+ # group appears later.
56
+ def conflicts(conflicts)
57
+ append(@conflicts, conflicts)
58
+ end
59
+
60
+ # @api private The config passed to Merger.new, including the daisyUI groups.
61
+ def merger_config
62
+ {
63
+ theme: @theme,
64
+ class_groups: DaisyGroups.build.merge(@class_groups) { |_id, existing, added| existing + added },
65
+ conflicting_class_groups: @conflicts
66
+ }
67
+ end
68
+
69
+ private
70
+
71
+ def append(target, entries)
72
+ entries.each { |id, values| (target[id.to_s] ||= []).concat(Array(values)) }
73
+ ClassMerge.reset!
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DaisyUI
4
+ module ClassMerge
5
+ # Builds merger class groups from the modifier groups components declare:
6
+ #
7
+ # register_modifiers(size: { sm: "btn-sm", lg: "btn-lg" })
8
+ #
9
+ # gives the group `daisy:group:btn-size` = ["btn-sm", "btn-lg"], so
10
+ # `btn-sm btn-lg` merges to `btn-lg`. Ungrouped modifiers never conflict.
11
+ # Tokens Tailwind already classifies (`bg-primary text-primary-content`)
12
+ # are left to the Tailwind groups, which resolve them per utility.
13
+ #
14
+ # Every gem component base class (`btn`, `table`) also gets a group of its
15
+ # own and is never dropped, even where Tailwind has a utility of the same
16
+ # name (`table`, `collapse`).
17
+ #
18
+ # @api private
19
+ module DaisyGroups
20
+ class << self
21
+ # Class groups for the merger: `{ "daisy:group:btn-size" => ["btn-xs", ...], ... }`.
22
+ def build(prefix: DaisyUI.configuration.prefix)
23
+ components = self.components
24
+ groups = {}
25
+
26
+ gem_components(components).each do |component|
27
+ next unless (base = component.component_class)
28
+
29
+ groups["daisy:base:#{base}"] = ["#{prefix}#{base}"]
30
+ end
31
+
32
+ components.each do |component|
33
+ component.modifier_groups.each do |group, members|
34
+ tokens = members.flat_map { |member| component.modifiers[member].to_s.split }
35
+ add(groups, "daisy:group:#{component.component_class || component.name}-#{group}", tokens, prefix)
36
+ end
37
+ end
38
+
39
+ # Configured modifiers join the component's own group of that name.
40
+ DaisyUI.configuration.modifiers.groups.each do |(component, group), classes|
41
+ owner = component ? component.component_class || component.name : "global"
42
+ add(groups, "daisy:group:#{owner}-#{group}", classes.flat_map(&:split), prefix)
43
+ end
44
+
45
+ groups
46
+ end
47
+
48
+ def tailwind_class?(token)
49
+ !ClassMerge.tailwind_utils.class_group_id(token).nil?
50
+ end
51
+
52
+ # Every named DaisyUI::Base subclass: the gem's components (eager
53
+ # loaded) and any app component loaded so far. An app component that
54
+ # loads later resets the merger through `register_modifiers`.
55
+ def components
56
+ Zeitwerk::Loader.eager_load_namespace(DaisyUI)
57
+ descendants(DaisyUI::Base).select(&:name)
58
+ end
59
+
60
+ # Only the gem's own base classes are protected: an app component's
61
+ # derived name (`UI::Grid` -> "grid") may well be a Tailwind utility.
62
+ def gem_components(components = self.components)
63
+ components.select { |component| component.name.start_with?("DaisyUI::") }
64
+ end
65
+
66
+ private
67
+
68
+ # Subclasses share their parent's component_class and group names, so
69
+ # their tokens land in the same group id and are unioned.
70
+ def add(groups, id, tokens, prefix)
71
+ tokens = tokens.reject { |token| tailwind_class?(token) }.map { |token| "#{prefix}#{token}" }
72
+ return if tokens.empty?
73
+
74
+ groups[id] = (groups[id] || []) | tokens
75
+ end
76
+
77
+ def descendants(klass)
78
+ klass.subclasses.flat_map { |subclass| [subclass, *descendants(subclass)] }
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Ported from tailwind_merge 1.5.6 (https://github.com/gjtorikian/tailwind_merge)
4
+ # Copyright (c) 2022 Garen J. Torikian. MIT License, see LICENSE-tailwind_merge.txt.
5
+
6
+ module DaisyUI
7
+ module ClassMerge
8
+ class Merger
9
+ include TailwindConfig
10
+ include ParseClassName
11
+ include SortModifiers
12
+
13
+ def initialize(config: {})
14
+ @config = merge_config(config)
15
+ @class_utils = ClassGroupUtils.new(@config)
16
+ @cache = Cache.new(@config[:cache_size])
17
+ @postfix_lookup_class_group_ids = build_postfix_lookup_class_group_ids(@config[:postfix_lookup_class_groups])
18
+ @order_sensitive_modifiers = Set.new(@config[:order_sensitive_modifiers]).freeze
19
+ end
20
+
21
+ def merge(classes)
22
+ normalized = classes.is_a?(Array) ? classes.compact.join(" ") : classes.to_s
23
+
24
+ @cache.getset(normalized) do
25
+ merge_class_list(normalized).freeze
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def merge_class_list(class_list)
32
+ # Set of class_group_ids in following format:
33
+ # `{importantModifier}{variantModifiers}{classGroupId}`
34
+ # @example 'float'
35
+ # @example 'hover:focus:bg-color'
36
+ # @example 'md:!pr'
37
+ class_names = class_list.split
38
+ return "" if class_names.empty?
39
+
40
+ class_groups_in_conflict = Set.new
41
+
42
+ merged_classes = []
43
+
44
+ class_names.reverse_each do |original_class_name|
45
+ result = parse_class_name(original_class_name, prefix: @config[:prefix])
46
+ is_external = result.is_external
47
+ modifiers = result.modifiers
48
+ has_important_modifier = result.has_important_modifier
49
+ base_class_name = result.base_class_name
50
+ maybe_postfix_modifier_position = result.maybe_postfix_modifier_position
51
+
52
+ if is_external
53
+ merged_classes.push(original_class_name)
54
+ next
55
+ end
56
+
57
+ has_postfix_modifier = maybe_postfix_modifier_position ? true : false
58
+
59
+ if has_postfix_modifier
60
+ # maybe_postfix_modifier_position is a byte offset (see ParseClassName)
61
+ base_class_name_without_postfix = base_class_name.byteslice(0, maybe_postfix_modifier_position)
62
+ class_group_id = @class_utils.class_group_id(base_class_name_without_postfix)
63
+
64
+ if class_group_id && @postfix_lookup_class_group_ids[class_group_id]
65
+ class_group_id_with_postfix = @class_utils.class_group_id(base_class_name)
66
+ if class_group_id_with_postfix && class_group_id_with_postfix != class_group_id
67
+ class_group_id = class_group_id_with_postfix
68
+ has_postfix_modifier = false
69
+ end
70
+ end
71
+ else
72
+ class_group_id = @class_utils.class_group_id(base_class_name)
73
+ end
74
+
75
+ unless class_group_id
76
+ unless has_postfix_modifier
77
+ # Not a Tailwind class
78
+ merged_classes << original_class_name
79
+ next
80
+ end
81
+
82
+ class_group_id = @class_utils.class_group_id(base_class_name)
83
+
84
+ unless class_group_id
85
+ # Not a Tailwind class
86
+ merged_classes << original_class_name
87
+ next
88
+ end
89
+
90
+ has_postfix_modifier = false
91
+ end
92
+
93
+ variant_modifier = sort_modifiers(modifiers, @order_sensitive_modifiers).join(":")
94
+
95
+ modifier_id = has_important_modifier ? "#{variant_modifier}#{IMPORTANT_MODIFIER}" : variant_modifier
96
+ has_modifier_id = !modifier_id.empty?
97
+ # `to_s` matches the previous interpolation behavior: group ids may be
98
+ # Symbols (custom configs) or Strings (defaults, where to_s is free)
99
+ class_id = has_modifier_id ? "#{modifier_id}#{class_group_id}" : class_group_id.to_s
100
+
101
+ # Tailwind class omitted due to conflict
102
+ next if class_groups_in_conflict.include?(class_id)
103
+
104
+ class_groups_in_conflict << class_id
105
+
106
+ @class_utils.get_conflicting_class_group_ids(class_group_id, has_postfix_modifier).each do |conflicting_id|
107
+ class_groups_in_conflict << (has_modifier_id ? "#{modifier_id}#{conflicting_id}" : conflicting_id.to_s)
108
+ end
109
+
110
+ # Tailwind class not in conflict
111
+ merged_classes << original_class_name
112
+ end
113
+
114
+ merged_classes.reverse!.join(" ")
115
+ end
116
+
117
+ def build_postfix_lookup_class_group_ids(class_group_ids)
118
+ lookup = {}
119
+ return lookup unless class_group_ids
120
+
121
+ class_group_ids.each { |id| lookup[id] = true }
122
+ lookup
123
+ end
124
+ end
125
+ end
126
+ end