daisyui 2.0.1 → 2.1.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 +78 -1
- data/README.md +81 -1
- data/app/javascript/daisy_ui/controllers/daisy_dropdown_controller.min.js +4 -0
- data/app/javascript/daisy_ui/controllers/daisy_dropdown_controller.min.js.map +10 -0
- data/app/javascript/daisy_ui/controllers/daisy_tooltip_controller.min.js +4 -0
- data/app/javascript/daisy_ui/controllers/daisy_tooltip_controller.min.js.map +10 -0
- data/config/importmap.rb +12 -3
- data/lib/daisy_ui/base.rb +87 -9
- data/lib/daisy_ui/chat.rb +1 -0
- data/lib/daisy_ui/checkbox.rb +1 -0
- data/lib/daisy_ui/class_merge/configuration.rb +18 -3
- data/lib/daisy_ui/class_merge/daisy_groups.rb +8 -5
- data/lib/daisy_ui/class_merge/tailwind_config.rb +8 -18
- data/lib/daisy_ui/class_merge/validators.rb +4 -0
- data/lib/daisy_ui/class_merge.rb +23 -12
- data/lib/daisy_ui/collapsible_sub_menu.rb +1 -0
- data/lib/daisy_ui/configurable/report_log.rb +36 -0
- data/lib/daisy_ui/configurable.rb +129 -5
- data/lib/daisy_ui/dropdown.rb +1 -0
- data/lib/daisy_ui/engine.rb +26 -9
- data/lib/daisy_ui/indicator.rb +1 -0
- data/lib/daisy_ui/mcp_server.rb +1 -0
- data/lib/daisy_ui/modal.rb +1 -0
- data/lib/daisy_ui/shareable.rb +56 -0
- data/lib/daisy_ui/steps.rb +1 -0
- data/lib/daisy_ui/store.rb +71 -0
- data/lib/daisy_ui/sub_menu.rb +2 -0
- data/lib/daisy_ui/tab.rb +6 -0
- data/lib/daisy_ui/tab_with_content.rb +1 -0
- data/lib/daisy_ui/tab_without_content.rb +1 -0
- data/lib/daisy_ui/tooltip.rb +1 -0
- data/lib/daisy_ui/unknown_modifier_error.rb +31 -0
- data/lib/daisy_ui/updated_at.rb +1 -1
- data/lib/daisy_ui/version.rb +1 -1
- metadata +9 -1
data/lib/daisy_ui/class_merge.rb
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "monitor"
|
|
4
|
-
|
|
5
3
|
module DaisyUI
|
|
6
4
|
# Merges CSS class lists so that later classes win over earlier conflicting
|
|
7
5
|
# ones, for Tailwind utilities and daisyUI modifiers alike:
|
|
@@ -16,10 +14,10 @@ module DaisyUI
|
|
|
16
14
|
module ClassMerge
|
|
17
15
|
TAILWIND_MERGE_VERSION = "1.5.6"
|
|
18
16
|
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
# The merger (with its LRU) and the Tailwind class groups live in
|
|
18
|
+
# DaisyUI::Store: one per Ractor, built on first use under the store's
|
|
19
|
+
# reentrant lock, so a reset! triggered while the merger is being built
|
|
20
|
+
# (e.g. a component file configuring DaisyUI as it loads) cannot deadlock.
|
|
23
21
|
class << self
|
|
24
22
|
# Accepts strings, nested arrays, nil and false (dropped, as Phlex does
|
|
25
23
|
# for `class:`). Returns a frozen String, "" when empty.
|
|
@@ -27,20 +25,33 @@ module DaisyUI
|
|
|
27
25
|
merger.merge(parts.flatten.select(&:itself).join(" "))
|
|
28
26
|
end
|
|
29
27
|
|
|
28
|
+
# Each Ractor's merger remembers the configuration it was built from
|
|
29
|
+
# and is rebuilt once DaisyUI.configure publishes another, also in a
|
|
30
|
+
# Ractor where reset! never ran.
|
|
30
31
|
def merger
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
configuration = DaisyUI.configuration
|
|
33
|
+
built_for, merger = Store.fetch(:daisy_ui_class_merger) { build_merger(configuration) }
|
|
34
|
+
return merger if built_for.equal?(configuration)
|
|
35
|
+
|
|
36
|
+
Store.delete(:daisy_ui_class_merger)
|
|
37
|
+
Store.fetch(:daisy_ui_class_merger) { build_merger(configuration) }.last
|
|
34
38
|
end
|
|
35
39
|
|
|
36
40
|
# @api private The stock Tailwind class groups, for classifying tokens.
|
|
37
41
|
def tailwind_utils
|
|
38
|
-
|
|
42
|
+
Store.fetch(:daisy_ui_tailwind_utils) { ClassGroupUtils.new(TailwindConfig::DEFAULTS) }
|
|
39
43
|
end
|
|
40
44
|
|
|
41
|
-
# Drops the
|
|
45
|
+
# Drops the current Ractor's merger so its next merge picks up
|
|
46
|
+
# configuration changes.
|
|
42
47
|
def reset!
|
|
43
|
-
|
|
48
|
+
Store.delete(:daisy_ui_class_merger)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def build_merger(configuration)
|
|
54
|
+
[configuration, Merger.new(config: configuration.class_merge.merger_config(configuration))]
|
|
44
55
|
end
|
|
45
56
|
end
|
|
46
57
|
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DaisyUI
|
|
4
|
+
module Configurable
|
|
5
|
+
# @api private The unknown modifiers already reported, so :warn and a
|
|
6
|
+
# callable fire once per key. Remembers up to `limit` keys, then starts
|
|
7
|
+
# over, so modifiers built from user input cannot grow it without bound.
|
|
8
|
+
# One per Ractor (DaisyUI::Store), so its Mutex is never shared.
|
|
9
|
+
class ReportLog
|
|
10
|
+
def initialize(limit)
|
|
11
|
+
@limit = limit
|
|
12
|
+
@keys = Set.new
|
|
13
|
+
@lock = Mutex.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# True the first time a key is seen.
|
|
17
|
+
def first?(key)
|
|
18
|
+
@lock.synchronize do
|
|
19
|
+
return false if @keys.include?(key)
|
|
20
|
+
|
|
21
|
+
@keys.clear if @keys.size >= @limit
|
|
22
|
+
@keys.add(key)
|
|
23
|
+
true
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def clear
|
|
28
|
+
@lock.synchronize { @keys.clear }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def size
|
|
32
|
+
@lock.synchronize { @keys.size }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -2,15 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
module DaisyUI
|
|
4
4
|
module Configurable
|
|
5
|
+
# Created when DaisyUI extends this module, at load, so `configuration` is
|
|
6
|
+
# a plain read with no check-then-set race.
|
|
7
|
+
def self.extended(base)
|
|
8
|
+
base.instance_variable_set(:@configuration, Configuration.new)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# Yields a writable copy of the configuration, then finalizes the copy
|
|
12
|
+
# (see finalize!) and publishes it. Calling configure again keeps
|
|
13
|
+
# working, no reader (another thread, a non-main Ractor) meets a
|
|
14
|
+
# half-changed or writable configuration, and a block that raises
|
|
15
|
+
# publishes nothing. Calls are serialized, so each starts from what the
|
|
16
|
+
# previous one published.
|
|
5
17
|
def configure
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
18
|
+
configure_lock.synchronize do
|
|
19
|
+
draft = configuration.dup
|
|
20
|
+
yield(draft) if block_given?
|
|
21
|
+
@configuration = draft.finalize!
|
|
22
|
+
ClassMerge.reset!
|
|
23
|
+
finalize!
|
|
24
|
+
configuration
|
|
25
|
+
end
|
|
10
26
|
end
|
|
11
27
|
|
|
12
28
|
def configuration
|
|
13
|
-
@configuration
|
|
29
|
+
@configuration
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Freezes what a non-main Ractor reads (this configuration and every
|
|
33
|
+
# component's registries) so DaisyUI::ClassMerge.merge runs in any Ractor.
|
|
34
|
+
# configure calls it, and the Rails engine calls it once the app has
|
|
35
|
+
# booted; an app with neither calls it at the end of boot. Afterwards
|
|
36
|
+
# change settings only through configure: writing to the frozen
|
|
37
|
+
# configuration raises. A configured value that cannot be made shareable
|
|
38
|
+
# is kept as it is (see Shareable.share), never raised on.
|
|
39
|
+
def finalize!
|
|
40
|
+
ClassMerge::DaisyGroups.components.each(&:finalize!)
|
|
41
|
+
configuration.finalize!
|
|
42
|
+
ClassMerge.tailwind_utils
|
|
43
|
+
self
|
|
14
44
|
end
|
|
15
45
|
|
|
16
46
|
class Modifiers
|
|
@@ -19,6 +49,19 @@ module DaisyUI
|
|
|
19
49
|
@groups = {}
|
|
20
50
|
end
|
|
21
51
|
|
|
52
|
+
def initialize_copy(source)
|
|
53
|
+
super
|
|
54
|
+
@modifiers = @modifiers.transform_values(&:dup)
|
|
55
|
+
@groups = @groups.dup
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @api private Called by DaisyUI.finalize!.
|
|
59
|
+
def finalize!
|
|
60
|
+
Shareable.share(@modifiers)
|
|
61
|
+
Shareable.share(@groups)
|
|
62
|
+
freeze
|
|
63
|
+
end
|
|
64
|
+
|
|
22
65
|
# `group:` makes the modifier an alternative to the component's other
|
|
23
66
|
# modifiers in that group, e.g. `add(:huge, classes: "btn-xl",
|
|
24
67
|
# component: DaisyUI::Button, group: :size)`.
|
|
@@ -52,12 +95,67 @@ module DaisyUI
|
|
|
52
95
|
end
|
|
53
96
|
|
|
54
97
|
class Configuration
|
|
98
|
+
UNKNOWN_MODIFIER_MODES = %i[raise warn ignore].freeze
|
|
99
|
+
REPORTED_UNKNOWN_MODIFIERS_LIMIT = 1_000
|
|
100
|
+
|
|
55
101
|
attr_reader :prefix
|
|
56
102
|
|
|
57
103
|
def initialize
|
|
58
104
|
@prefix = nil
|
|
59
105
|
end
|
|
60
106
|
|
|
107
|
+
def initialize_copy(source)
|
|
108
|
+
super
|
|
109
|
+
@class_merge = @class_merge&.dup
|
|
110
|
+
@modifiers = @modifiers&.dup
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# @api private Called by DaisyUI.configure and DaisyUI.finalize!. A
|
|
114
|
+
# callable unknown_modifiers that cannot be made shareable (a lambda
|
|
115
|
+
# that closes over app state, an object holding a logger) is kept: the
|
|
116
|
+
# configuration is then frozen but readable on the main Ractor only.
|
|
117
|
+
def finalize!
|
|
118
|
+
class_merge.finalize!
|
|
119
|
+
modifiers.finalize!
|
|
120
|
+
Shareable.share(@prefix)
|
|
121
|
+
Shareable.share(@unknown_modifiers)
|
|
122
|
+
freeze
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# What a component does with a positional modifier it does not know:
|
|
126
|
+
# :raise, :warn (once per component and modifier), :ignore, or a callable
|
|
127
|
+
# that receives the DaisyUI::UnknownModifierError (once per component and
|
|
128
|
+
# modifier), e.g. `->(error) { Appsignal.report_error(error) }`.
|
|
129
|
+
#
|
|
130
|
+
# Defaults to :raise under Rails in development and test, :warn otherwise.
|
|
131
|
+
def unknown_modifiers
|
|
132
|
+
@unknown_modifiers || default_unknown_modifiers
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def unknown_modifiers=(mode)
|
|
136
|
+
valid = mode.nil? || mode.respond_to?(:call) || UNKNOWN_MODIFIER_MODES.include?(mode)
|
|
137
|
+
raise ArgumentError, "unknown_modifiers must be :raise, :warn, :ignore or a callable, got #{mode.inspect}" unless valid
|
|
138
|
+
|
|
139
|
+
@unknown_modifiers = mode
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# @api private Builds the error only when it will be raised or reported.
|
|
143
|
+
def report_unknown_modifier(component:, modifier:, known:, part: nil)
|
|
144
|
+
mode = unknown_modifiers
|
|
145
|
+
return if mode == :ignore
|
|
146
|
+
|
|
147
|
+
error = -> { UnknownModifierError.new(component:, modifier:, known:, part:) }
|
|
148
|
+
raise error.call if mode == :raise
|
|
149
|
+
return unless first_report?([component, part, modifier])
|
|
150
|
+
|
|
151
|
+
mode == :warn ? warn("[DaisyUI] #{error.call.message}") : mode.call(error.call)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# @api private
|
|
155
|
+
def reset_reported_unknown_modifiers!
|
|
156
|
+
reported_unknown_modifiers.clear
|
|
157
|
+
end
|
|
158
|
+
|
|
61
159
|
def prefix=(prefix)
|
|
62
160
|
@prefix = prefix
|
|
63
161
|
ClassMerge.reset!
|
|
@@ -70,6 +168,32 @@ module DaisyUI
|
|
|
70
168
|
def modifiers
|
|
71
169
|
@modifiers ||= Modifiers.new
|
|
72
170
|
end
|
|
171
|
+
|
|
172
|
+
private
|
|
173
|
+
|
|
174
|
+
def first_report?(key)
|
|
175
|
+
reported_unknown_modifiers.first?(key)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# What was already reported lives per Ractor, outside the configuration,
|
|
179
|
+
# which stays shareable.
|
|
180
|
+
def reported_unknown_modifiers
|
|
181
|
+
Store.fetch(:daisy_ui_reported_unknown_modifiers) { ReportLog.new(REPORTED_UNKNOWN_MODIFIERS_LIMIT) }
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def default_unknown_modifiers
|
|
185
|
+
return :warn unless defined?(::Rails) && ::Rails.respond_to?(:env)
|
|
186
|
+
|
|
187
|
+
%w[development test].include?(::Rails.env.to_s) ? :raise : :warn
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
private
|
|
192
|
+
|
|
193
|
+
# Reentrant, and one per Ractor (DaisyUI::Store) so it is never shared;
|
|
194
|
+
# configure writes DaisyUI's ivar, so only the main Ractor gets that far.
|
|
195
|
+
def configure_lock
|
|
196
|
+
Store.fetch(:daisy_ui_configure_lock) { Monitor.new }
|
|
73
197
|
end
|
|
74
198
|
end
|
|
75
199
|
end
|
data/lib/daisy_ui/dropdown.rb
CHANGED
|
@@ -5,6 +5,7 @@ require "securerandom"
|
|
|
5
5
|
module DaisyUI
|
|
6
6
|
class Dropdown < Base
|
|
7
7
|
self.component_class = :dropdown
|
|
8
|
+
register_flags :popover, :tap_to_close
|
|
8
9
|
|
|
9
10
|
# Placement modifiers that, in :popover mode, must live on the popover
|
|
10
11
|
# element itself (DaisyUI's `position-area` rule is on `.dropdown`).
|
data/lib/daisy_ui/engine.rb
CHANGED
|
@@ -4,26 +4,43 @@ module DaisyUI
|
|
|
4
4
|
# Optional Rails integration. Loaded only when Rails::Engine is defined (see
|
|
5
5
|
# lib/daisy_ui.rb), so the gem stays a plain Phlex library outside Rails.
|
|
6
6
|
#
|
|
7
|
-
# It exposes the gem's bundled
|
|
8
|
-
#
|
|
9
|
-
# *
|
|
10
|
-
#
|
|
11
|
-
# manual pin;
|
|
7
|
+
# It exposes the gem's bundled Stimulus controllers (`daisy-dropdown`,
|
|
8
|
+
# `daisy-tooltip`) to the host app:
|
|
9
|
+
# * adds config/importmap.rb to importmap-rails' paths, which pins each
|
|
10
|
+
# `daisy_ui/controllers/<name>` to its minified build (no manual pin);
|
|
12
11
|
# * appends the JS dir to the asset load path so Propshaft/Sprockets serve
|
|
13
|
-
# the
|
|
12
|
+
# the .min.js and its sourcemap (Sprockets also needs them precompiled).
|
|
14
13
|
#
|
|
15
|
-
# The
|
|
16
|
-
# true)`), so
|
|
14
|
+
# The controllers are opt-in at the markup level (`Dropdown(:popover, stimulus:
|
|
15
|
+
# true)`, `Tooltip(:popover)`) and not preloaded, so a page that never uses
|
|
16
|
+
# one never fetches it.
|
|
17
17
|
# Asset-only engine: no isolate_namespace, since it exposes no routes, models,
|
|
18
18
|
# or helpers — only static JavaScript — and namespacing would risk surprises in
|
|
19
19
|
# the host app for no benefit.
|
|
20
20
|
class Engine < ::Rails::Engine
|
|
21
21
|
JAVASCRIPT_PATH = root.join("app/javascript")
|
|
22
|
+
# The minified controllers config/importmap.rb pins, and their sourcemaps.
|
|
23
|
+
MINIFIED_ASSETS = Dir[JAVASCRIPT_PATH.join("daisy_ui/controllers/*.min.js{,.map}").to_s]
|
|
24
|
+
.map { |path| Pathname.new(path).relative_path_from(JAVASCRIPT_PATH).to_s }.sort.freeze
|
|
22
25
|
|
|
23
26
|
initializer "daisy_ui.assets" do |app|
|
|
24
|
-
|
|
27
|
+
next unless app.config.respond_to?(:assets)
|
|
28
|
+
|
|
29
|
+
app.config.assets.paths << JAVASCRIPT_PATH.to_s
|
|
30
|
+
# Sprockets compiles only what is listed; Propshaft compiles every file in
|
|
31
|
+
# paths. The list is nil unless Sprockets or another engine created it,
|
|
32
|
+
# and OrderedOptions answers respond_to? for any name, so check the value.
|
|
33
|
+
precompile = app.config.assets.precompile
|
|
34
|
+
precompile.concat(MINIFIED_ASSETS) if precompile.is_a?(Array)
|
|
25
35
|
end
|
|
26
36
|
|
|
37
|
+
# Once the app has booted, freeze the configuration and component
|
|
38
|
+
# registries so a non-main Ractor can merge classes. DaisyUI.configure in
|
|
39
|
+
# an initializer already finalized; this also covers an app that never
|
|
40
|
+
# calls it and the components loaded during boot (eager loading).
|
|
41
|
+
# DaisyUI.configure keeps working afterwards, on a copy.
|
|
42
|
+
config.after_initialize { DaisyUI.finalize! }
|
|
43
|
+
|
|
27
44
|
initializer "daisy_ui.importmap", before: "importmap" do |app|
|
|
28
45
|
next unless app.config.respond_to?(:importmap)
|
|
29
46
|
|
data/lib/daisy_ui/indicator.rb
CHANGED
|
@@ -19,6 +19,7 @@ module DaisyUI
|
|
|
19
19
|
private
|
|
20
20
|
|
|
21
21
|
def build_item_classes(item_modifiers, options)
|
|
22
|
+
report_unknown_modifiers(item_modifiers, modifier_map.keys, part: :item)
|
|
22
23
|
# Start with base class
|
|
23
24
|
base = apply_prefix("indicator-item")
|
|
24
25
|
# Add modifier classes from position modifiers
|
data/lib/daisy_ui/mcp_server.rb
CHANGED
data/lib/daisy_ui/modal.rb
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DaisyUI
|
|
4
|
+
# @api private Deep-freezes a value so a non-main Ractor can read it from a
|
|
5
|
+
# constant or a class ivar: `Ractor.make_shareable`, or, on engines without
|
|
6
|
+
# Ractor (JRuby, TruffleRuby), a walk freezing Hash values and Array elements.
|
|
7
|
+
module Shareable
|
|
8
|
+
class << self
|
|
9
|
+
# For the gem's own literals, which must all be shareable: raises if one
|
|
10
|
+
# is not.
|
|
11
|
+
def make(value)
|
|
12
|
+
ractor? ? ::Ractor.make_shareable(value) : deep_freeze(value)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# For what an app configures. Freezes Hashes, Arrays and Strings all the
|
|
16
|
+
# way down, makes a capture-free Proc shareable, and keeps anything else
|
|
17
|
+
# (a Proc that closes over app state, a logger) as it is, so whatever
|
|
18
|
+
# holds it is readable on the main Ractor only. Never raises, and never
|
|
19
|
+
# hands an object it does not know to Ractor.make_shareable, which
|
|
20
|
+
# freezes as it goes and would leave a logger frozen before failing.
|
|
21
|
+
def share(value)
|
|
22
|
+
case value
|
|
23
|
+
when Hash
|
|
24
|
+
value.each do |key, member|
|
|
25
|
+
share(key)
|
|
26
|
+
share(member)
|
|
27
|
+
end.freeze
|
|
28
|
+
when Array then value.each { |member| share(member) }.freeze
|
|
29
|
+
when String then value.freeze
|
|
30
|
+
when Proc then share_proc(value)
|
|
31
|
+
else value
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def ractor?
|
|
36
|
+
defined?(::Ractor) ? ::Ractor.respond_to?(:make_shareable) : false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def share_proc(callable)
|
|
42
|
+
ractor? ? ::Ractor.make_shareable(callable) : callable
|
|
43
|
+
rescue ::Ractor::Error, ArgumentError
|
|
44
|
+
callable
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def deep_freeze(value)
|
|
48
|
+
case value
|
|
49
|
+
when Hash then value.each_value { |member| deep_freeze(member) }
|
|
50
|
+
when Array then value.each { |member| deep_freeze(member) }
|
|
51
|
+
end
|
|
52
|
+
value.freeze
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/daisy_ui/steps.rb
CHANGED
|
@@ -27,6 +27,7 @@ module DaisyUI
|
|
|
27
27
|
private
|
|
28
28
|
|
|
29
29
|
def build_step_classes(step_modifiers, options)
|
|
30
|
+
report_unknown_modifiers(step_modifiers, step_modifier_map.keys, part: :step)
|
|
30
31
|
base = apply_prefix("step")
|
|
31
32
|
mod_classes = step_modifiers.filter_map { |m| apply_prefix(step_modifier_map[m]) }
|
|
32
33
|
custom = options.delete(:class)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "monitor"
|
|
4
|
+
|
|
5
|
+
module DaisyUI
|
|
6
|
+
# @api private Per-Ractor lazy storage for caches that must not live in a
|
|
7
|
+
# class ivar or a constant (the class-merge merger, the Tailwind class
|
|
8
|
+
# groups, the unknown-modifier report log):
|
|
9
|
+
#
|
|
10
|
+
# DaisyUI::Store.fetch(:daisy_ui_merger) { Merger.new(...) }
|
|
11
|
+
# DaisyUI::Store.delete(:daisy_ui_merger)
|
|
12
|
+
#
|
|
13
|
+
# Adapted from conformance-kit's ConformanceKit::Store (MIT, same author),
|
|
14
|
+
# which daisyui cannot depend on at runtime, plus `delete`. Every Ractor has
|
|
15
|
+
# its own Monitor-guarded table: reentrant, so an initializer may fetch or
|
|
16
|
+
# delete other keys. On Ruby 3.4+ the table comes from
|
|
17
|
+
# `Ractor.store_if_absent`. Below 3.4 the main Ractor's table is created at
|
|
18
|
+
# load (never lazily, so there is no check-then-set race), and a non-main
|
|
19
|
+
# Ractor keeps its own in Ractor-local storage.
|
|
20
|
+
module Store
|
|
21
|
+
# One Ractor's values. Reads skip the lock once a key is stored.
|
|
22
|
+
class Table
|
|
23
|
+
def initialize
|
|
24
|
+
@monitor = Monitor.new
|
|
25
|
+
@values = {}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def fetch(key)
|
|
29
|
+
@values.fetch(key) do
|
|
30
|
+
@monitor.synchronize { @values.fetch(key) { @values[key] = yield } }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def delete(key)
|
|
35
|
+
@monitor.synchronize { @values.delete(key) }
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class << self
|
|
40
|
+
def fetch(key, &init)
|
|
41
|
+
raise ArgumentError, "DaisyUI::Store.fetch(#{key.inspect}) needs an initializer block" unless init
|
|
42
|
+
|
|
43
|
+
table.fetch(key, &init)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def delete(key)
|
|
47
|
+
table.delete(key)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def main_ractor?
|
|
51
|
+
!defined?(::Ractor) || ::Ractor.current == ::Ractor.main
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def native?
|
|
55
|
+
defined?(::Ractor) ? ::Ractor.respond_to?(:store_if_absent) : false
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def table
|
|
61
|
+
return ::Ractor.store_if_absent(:daisy_ui_store) { Table.new } if native?
|
|
62
|
+
return ::Ractor.current[:daisy_ui_store] ||= Table.new unless main_ractor?
|
|
63
|
+
|
|
64
|
+
# Set once below, at load, below Ruby 3.4; only the main Ractor reads it.
|
|
65
|
+
@main_table # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
@main_table = Table.new unless native?
|
|
70
|
+
end
|
|
71
|
+
end
|
data/lib/daisy_ui/sub_menu.rb
CHANGED
data/lib/daisy_ui/tab.rb
CHANGED
|
@@ -36,5 +36,11 @@ module DaisyUI
|
|
|
36
36
|
div role: :tabpanel, class: content_classes, **options, &block
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
# Tab only dispatches: TabWithContent or TabWithoutContent validates the
|
|
43
|
+
# modifiers when it is built.
|
|
44
|
+
def validate_modifiers; end
|
|
39
45
|
end
|
|
40
46
|
end
|
data/lib/daisy_ui/tooltip.rb
CHANGED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "did_you_mean"
|
|
4
|
+
|
|
5
|
+
module DaisyUI
|
|
6
|
+
# Raised (or warned, or reported) when a component is given a positional
|
|
7
|
+
# modifier it does not know. See `DaisyUI.configuration.unknown_modifiers`.
|
|
8
|
+
class UnknownModifierError < ArgumentError
|
|
9
|
+
attr_reader :component, :modifier, :part
|
|
10
|
+
|
|
11
|
+
# `part` names the sub-component method (e.g. :bubble for Chat#bubble).
|
|
12
|
+
def initialize(component:, modifier:, known:, part: nil)
|
|
13
|
+
@component = component
|
|
14
|
+
@modifier = modifier
|
|
15
|
+
@part = part
|
|
16
|
+
super(build_message(known))
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def build_message(known)
|
|
22
|
+
name = component.name || component.inspect
|
|
23
|
+
name = "#{name}##{part}" if part
|
|
24
|
+
message = "#{name} has no modifier #{modifier.inspect}"
|
|
25
|
+
suggestions = DidYouMean::SpellChecker.new(dictionary: known.map(&:to_s)).correct(modifier.to_s)
|
|
26
|
+
return message if suggestions.empty?
|
|
27
|
+
|
|
28
|
+
"#{message}. Did you mean? #{suggestions.map { |s| ":#{s}" }.join(', ')}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/daisy_ui/updated_at.rb
CHANGED
data/lib/daisy_ui/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: daisyui
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mikael Henriksson
|
|
@@ -63,7 +63,11 @@ files:
|
|
|
63
63
|
- LICENSE.txt
|
|
64
64
|
- README.md
|
|
65
65
|
- app/javascript/daisy_ui/controllers/daisy_dropdown_controller.js
|
|
66
|
+
- app/javascript/daisy_ui/controllers/daisy_dropdown_controller.min.js
|
|
67
|
+
- app/javascript/daisy_ui/controllers/daisy_dropdown_controller.min.js.map
|
|
66
68
|
- app/javascript/daisy_ui/controllers/daisy_tooltip_controller.js
|
|
69
|
+
- app/javascript/daisy_ui/controllers/daisy_tooltip_controller.min.js
|
|
70
|
+
- app/javascript/daisy_ui/controllers/daisy_tooltip_controller.min.js.map
|
|
67
71
|
- config/importmap.rb
|
|
68
72
|
- exe/daisyui-mcp
|
|
69
73
|
- lib/daisy_ui.rb
|
|
@@ -94,6 +98,7 @@ files:
|
|
|
94
98
|
- lib/daisy_ui/collapse.rb
|
|
95
99
|
- lib/daisy_ui/collapsible_sub_menu.rb
|
|
96
100
|
- lib/daisy_ui/configurable.rb
|
|
101
|
+
- lib/daisy_ui/configurable/report_log.rb
|
|
97
102
|
- lib/daisy_ui/countdown.rb
|
|
98
103
|
- lib/daisy_ui/diff.rb
|
|
99
104
|
- lib/daisy_ui/divider.rb
|
|
@@ -137,11 +142,13 @@ files:
|
|
|
137
142
|
- lib/daisy_ui/range.rb
|
|
138
143
|
- lib/daisy_ui/rating.rb
|
|
139
144
|
- lib/daisy_ui/select.rb
|
|
145
|
+
- lib/daisy_ui/shareable.rb
|
|
140
146
|
- lib/daisy_ui/skeleton.rb
|
|
141
147
|
- lib/daisy_ui/stack.rb
|
|
142
148
|
- lib/daisy_ui/stat.rb
|
|
143
149
|
- lib/daisy_ui/status.rb
|
|
144
150
|
- lib/daisy_ui/steps.rb
|
|
151
|
+
- lib/daisy_ui/store.rb
|
|
145
152
|
- lib/daisy_ui/sub_menu.rb
|
|
146
153
|
- lib/daisy_ui/swap.rb
|
|
147
154
|
- lib/daisy_ui/tab.rb
|
|
@@ -157,6 +164,7 @@ files:
|
|
|
157
164
|
- lib/daisy_ui/toast.rb
|
|
158
165
|
- lib/daisy_ui/toggle.rb
|
|
159
166
|
- lib/daisy_ui/tooltip.rb
|
|
167
|
+
- lib/daisy_ui/unknown_modifier_error.rb
|
|
160
168
|
- lib/daisy_ui/updated_at.rb
|
|
161
169
|
- lib/daisy_ui/validator.rb
|
|
162
170
|
- lib/daisy_ui/version.rb
|