vident 1.0.0.beta1 → 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 +41 -0
- data/README.md +177 -23
- data/lib/generators/vident/install/install_generator.rb +53 -0
- data/lib/generators/vident/install/templates/vident.rb +20 -0
- data/lib/vident/caching.rb +3 -9
- data/lib/vident/child_element_helper.rb +64 -0
- data/lib/vident/component.rb +4 -11
- data/lib/vident/component_attribute_resolver.rb +21 -36
- data/lib/vident/component_class_lists.rb +4 -3
- data/lib/vident/stable_id.rb +48 -17
- data/lib/vident/stimulus/naming.rb +19 -0
- data/lib/vident/stimulus/primitive.rb +38 -0
- data/lib/vident/stimulus.rb +31 -0
- data/lib/vident/stimulus_action.rb +58 -23
- data/lib/vident/stimulus_attribute_base.rb +27 -23
- data/lib/vident/stimulus_attributes.rb +56 -185
- data/lib/vident/stimulus_builder.rb +66 -87
- data/lib/vident/stimulus_class.rb +3 -9
- data/lib/vident/stimulus_class_collection.rb +1 -5
- data/lib/vident/stimulus_collection_base.rb +4 -12
- data/lib/vident/stimulus_component.rb +8 -7
- data/lib/vident/stimulus_controller.rb +10 -13
- data/lib/vident/stimulus_data_attribute_builder.rb +15 -74
- data/lib/vident/stimulus_helper.rb +4 -12
- data/lib/vident/stimulus_null.rb +21 -0
- data/lib/vident/stimulus_outlet.rb +4 -11
- data/lib/vident/stimulus_outlet_collection.rb +1 -5
- data/lib/vident/stimulus_param.rb +42 -0
- data/lib/vident/stimulus_param_collection.rb +11 -0
- data/lib/vident/stimulus_target.rb +7 -17
- data/lib/vident/stimulus_target_collection.rb +2 -6
- data/lib/vident/stimulus_value.rb +14 -44
- data/lib/vident/stimulus_value_collection.rb +1 -5
- data/lib/vident/tailwind.rb +0 -2
- data/lib/vident/version.rb +1 -1
- data/lib/vident.rb +8 -13
- data/skills/vident/SKILL.md +628 -0
- metadata +11 -2
- data/lib/vident/tag_helper.rb +0 -65
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
module Vident
|
|
4
4
|
module StimulusAttributes
|
|
5
5
|
extend ActiveSupport::Concern
|
|
6
|
+
# `extend` + `include` so Naming helpers are callable both in the module
|
|
7
|
+
# body (outside define_method args) and inside define_method blocks
|
|
8
|
+
# (at instance call-time).
|
|
9
|
+
extend Stimulus::Naming
|
|
10
|
+
include Stimulus::Naming
|
|
6
11
|
|
|
7
12
|
class_methods do
|
|
8
|
-
#
|
|
9
|
-
# so that the action parser will see it as a Stimulus event.
|
|
13
|
+
# Symbol so the action parser treats it as a Stimulus event type.
|
|
10
14
|
def stimulus_scoped_event(event)
|
|
11
15
|
:"#{component_name}:#{stimulus_js_name(event)}"
|
|
12
16
|
end
|
|
@@ -17,229 +21,95 @@ module Vident
|
|
|
17
21
|
|
|
18
22
|
private
|
|
19
23
|
|
|
20
|
-
def stimulus_js_name(name)
|
|
21
|
-
name.to_s.camelize(:lower)
|
|
22
|
-
end
|
|
24
|
+
def stimulus_js_name(name) = name.to_s.camelize(:lower)
|
|
23
25
|
end
|
|
24
26
|
|
|
25
|
-
# Parse inputs to create a StimulusController instance representing a Stimulus controller attribute
|
|
26
|
-
# examples:
|
|
27
|
-
# stimulus_controller("my_controller") => StimulusController that converts to {"controller" => "my-controller"}
|
|
28
|
-
# stimulus_controller("path/to/controller") => StimulusController that converts to {"controller" => "path--to--controller"}
|
|
29
|
-
# stimulus_controller() => StimulusController that uses implied controller name
|
|
30
27
|
def stimulus_controller(*args)
|
|
31
28
|
return args.first if args.length == 1 && args.first.is_a?(StimulusController)
|
|
32
29
|
StimulusController.new(*args, implied_controller: implied_controller_path)
|
|
33
30
|
end
|
|
34
31
|
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
32
|
+
# Plural parsers `stimulus_<kind>s(*args)` — generated from the primitives
|
|
33
|
+
# registry below. Each accepts: pre-built Value (pass-through), pre-built
|
|
34
|
+
# Collection (unwrapped; a single one is returned as-is), Array (splatted
|
|
35
|
+
# into the singular builder), Hash (expanded per-pair for
|
|
36
|
+
# `hash_expands: true`, single-arg descriptor otherwise), else passed to
|
|
37
|
+
# the singular builder. Methods defined this way: `stimulus_controllers`,
|
|
38
|
+
# `stimulus_actions`, `stimulus_targets`, `stimulus_outlets`,
|
|
39
|
+
# `stimulus_values`, `stimulus_params`, `stimulus_classes`.
|
|
40
|
+
Stimulus::PRIMITIVES.each do |primitive|
|
|
41
|
+
define_method(primitive.key) do |*args|
|
|
42
|
+
collection_class = primitive.collection_class
|
|
43
|
+
return collection_class.new if args.empty? || args.all?(&:blank?)
|
|
44
|
+
return args.first if args.length == 1 && args.first.is_a?(collection_class)
|
|
45
|
+
|
|
46
|
+
singular = primitive.singular_key
|
|
47
|
+
converted = []
|
|
48
|
+
args.each do |arg|
|
|
49
|
+
case arg
|
|
50
|
+
when primitive.value_class then converted << arg
|
|
51
|
+
when collection_class then converted.concat(arg.to_a)
|
|
52
|
+
when Hash
|
|
53
|
+
if primitive.keyed?
|
|
54
|
+
arg.each { |name, val| converted << send(singular, name, val) }
|
|
55
|
+
else
|
|
56
|
+
converted << send(singular, arg)
|
|
57
|
+
end
|
|
58
|
+
when Array then converted << send(singular, *arg)
|
|
59
|
+
else converted << send(singular, arg)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
collection_class.new(converted)
|
|
44
63
|
end
|
|
45
|
-
StimulusControllerCollection.new(converted_controllers)
|
|
46
64
|
end
|
|
47
65
|
|
|
48
|
-
# Parse inputs to create a StimulusAction instance representing a Stimulus action attribute
|
|
49
|
-
# examples:
|
|
50
|
-
# stimulus_action(:my_thing) => StimulusAction that converts to "current_controller#myThing"
|
|
51
|
-
# stimulus_action(:click, :my_thing) => StimulusAction that converts to "click->current_controller#myThing"
|
|
52
|
-
# stimulus_action("click->current_controller#myThing") => StimulusAction that converts to "click->current_controller#myThing"
|
|
53
|
-
# stimulus_action("path/to/current", :my_thing) => StimulusAction that converts to "path--to--current_controller#myThing"
|
|
54
|
-
# stimulus_action(:click, "path/to/current", :my_thing) => StimulusAction that converts to "click->path--to--current_controller#myThing"
|
|
55
66
|
def stimulus_action(*args)
|
|
56
67
|
return args.first if args.length == 1 && args.first.is_a?(StimulusAction)
|
|
57
68
|
StimulusAction.new(*args, implied_controller:)
|
|
58
69
|
end
|
|
59
70
|
|
|
60
|
-
# Parse inputs to create a StimulusActionCollection instance representing multiple Stimulus actions
|
|
61
|
-
def stimulus_actions(*actions)
|
|
62
|
-
return StimulusActionCollection.new if actions.empty? || actions.all?(&:blank?)
|
|
63
|
-
|
|
64
|
-
converted_actions = actions.map do |action|
|
|
65
|
-
action.is_a?(Array) ? stimulus_action(*action) : stimulus_action(action)
|
|
66
|
-
end
|
|
67
|
-
StimulusActionCollection.new(converted_actions)
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
# Parse inputs to create a StimulusTarget instance representing a Stimulus target attribute
|
|
71
|
-
# examples:
|
|
72
|
-
# stimulus_target(:my_target) => StimulusTarget that converts to {"current_controller-target" => "myTarget"}
|
|
73
|
-
# stimulus_target("path/to/current", :my_target) => StimulusTarget that converts to {"path--to--current-target" => "myTarget"}
|
|
74
71
|
def stimulus_target(*args)
|
|
75
72
|
return args.first if args.length == 1 && args.first.is_a?(StimulusTarget)
|
|
76
73
|
StimulusTarget.new(*args, implied_controller:)
|
|
77
74
|
end
|
|
78
75
|
|
|
79
|
-
#
|
|
80
|
-
|
|
81
|
-
return StimulusTargetCollection.new if targets.empty? || targets.all?(&:blank?)
|
|
82
|
-
|
|
83
|
-
converted_targets = targets.map do |target|
|
|
84
|
-
target.is_a?(Array) ? stimulus_target(*target) : stimulus_target(target)
|
|
85
|
-
end
|
|
86
|
-
StimulusTargetCollection.new(converted_targets)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Parse inputs to create a StimulusOutlet instance representing a Stimulus outlet attribute
|
|
90
|
-
# examples:
|
|
91
|
-
# stimulus_outlet(:user_status, ".online-user") => StimulusOutlet that converts to {"current_controller-user-status-outlet" => ".online-user"}
|
|
92
|
-
# stimulus_outlet("path/to/current", :user_status, ".online-user") => StimulusOutlet that converts to {"path--to--current-user-status-outlet" => ".online-user"}
|
|
93
|
-
# stimulus_outlet(:user_status) => StimulusOutlet with auto-generated selector
|
|
94
|
-
# stimulus_outlet(component_instance) => StimulusOutlet from component
|
|
76
|
+
# `component_id: @id` scopes the auto-generated selector to this component
|
|
77
|
+
# instance (e.g. `#<host-id> [data-controller~=<outlet>]`).
|
|
95
78
|
def stimulus_outlet(*args)
|
|
96
79
|
return args.first if args.length == 1 && args.first.is_a?(StimulusOutlet)
|
|
97
80
|
StimulusOutlet.new(*args, implied_controller:, component_id: @id)
|
|
98
81
|
end
|
|
99
82
|
|
|
100
|
-
# Parse inputs to create a StimulusOutletCollection instance representing multiple Stimulus outlets
|
|
101
|
-
def stimulus_outlets(*outlets)
|
|
102
|
-
return StimulusOutletCollection.new if outlets.empty? || outlets.all?(&:blank?)
|
|
103
|
-
|
|
104
|
-
converted_outlets = []
|
|
105
|
-
outlets.each do |outlet|
|
|
106
|
-
if outlet.is_a?(Hash)
|
|
107
|
-
# Hash format: {name: selector, other_name: other_selector} - expands to multiple outlets
|
|
108
|
-
outlet.each { |name, selector| converted_outlets << stimulus_outlet(name, selector) }
|
|
109
|
-
elsif outlet.is_a?(Array)
|
|
110
|
-
# Array format: [name, selector] - splat into stimulus_outlet
|
|
111
|
-
converted_outlets << stimulus_outlet(*outlet)
|
|
112
|
-
else
|
|
113
|
-
converted_outlets << stimulus_outlet(outlet)
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
StimulusOutletCollection.new(converted_outlets)
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Parse inputs to create a StimulusValue instance representing a Stimulus value attribute
|
|
120
|
-
# examples:
|
|
121
|
-
# stimulus_value(:url, "https://example.com") => StimulusValue that converts to {"current_controller-url-value" => "https://example.com"}
|
|
122
|
-
# stimulus_value("path/to/current", :url, "https://example.com") => StimulusValue that converts to {"path--to--current-url-value" => "https://example.com"}
|
|
123
83
|
def stimulus_value(*args)
|
|
124
84
|
return args.first if args.length == 1 && args.first.is_a?(StimulusValue)
|
|
125
85
|
StimulusValue.new(*args, implied_controller:)
|
|
126
86
|
end
|
|
127
87
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
converted_values = []
|
|
133
|
-
|
|
134
|
-
values.each do |value|
|
|
135
|
-
if value.is_a?(Hash)
|
|
136
|
-
# Hash format: {name: value, other_name: other_value} - expands to multiple values
|
|
137
|
-
value.each { |name, val| converted_values << stimulus_value(name, val) }
|
|
138
|
-
elsif value.is_a?(Array)
|
|
139
|
-
# Array format: [controller, name, value] or [name, value] - splat into stimulus_value
|
|
140
|
-
converted_values << stimulus_value(*value)
|
|
141
|
-
else
|
|
142
|
-
converted_values << stimulus_value(value)
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
StimulusValueCollection.new(converted_values)
|
|
88
|
+
def stimulus_param(*args)
|
|
89
|
+
return args.first if args.length == 1 && args.first.is_a?(StimulusParam)
|
|
90
|
+
StimulusParam.new(*args, implied_controller:)
|
|
147
91
|
end
|
|
148
92
|
|
|
149
|
-
# Parse inputs to create a StimulusClass instance representing a Stimulus class attribute
|
|
150
|
-
# examples:
|
|
151
|
-
# stimulus_class(:loading, "spinner active") => StimulusClass that converts to {"current_controller-loading-class" => "spinner active"}
|
|
152
|
-
# stimulus_class("path/to/current", :loading, ["spinner", "active"]) => StimulusClass that converts to {"path--to--current-loading-class" => "spinner active"}
|
|
153
93
|
def stimulus_class(*args)
|
|
154
94
|
return args.first if args.length == 1 && args.first.is_a?(StimulusClass)
|
|
155
95
|
StimulusClass.new(*args, implied_controller:)
|
|
156
96
|
end
|
|
157
97
|
|
|
158
|
-
#
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
cls.each { |name, class_list| converted_classes << stimulus_class(name, class_list) }
|
|
168
|
-
elsif cls.is_a?(Array)
|
|
169
|
-
# Array format: [controller, name, classes] or [name, classes] - splat into stimulus_class
|
|
170
|
-
converted_classes << stimulus_class(*cls)
|
|
171
|
-
else
|
|
172
|
-
converted_classes << stimulus_class(cls)
|
|
173
|
-
end
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
StimulusClassCollection.new(converted_classes)
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
# Methods to add to the stimulus collections
|
|
180
|
-
|
|
181
|
-
def add_stimulus_controllers(controllers)
|
|
182
|
-
s_controllers = stimulus_controllers(*Array.wrap(controllers))
|
|
183
|
-
@stimulus_controllers_collection = if @stimulus_controllers_collection
|
|
184
|
-
@stimulus_controllers_collection.merge(s_controllers)
|
|
185
|
-
else
|
|
186
|
-
s_controllers
|
|
187
|
-
end
|
|
188
|
-
end
|
|
189
|
-
|
|
190
|
-
def add_stimulus_actions(actions)
|
|
191
|
-
s_actions = stimulus_actions(*Array.wrap(actions))
|
|
192
|
-
@stimulus_actions_collection = if @stimulus_actions_collection
|
|
193
|
-
@stimulus_actions_collection.merge(s_actions)
|
|
194
|
-
else
|
|
195
|
-
s_actions
|
|
98
|
+
# Mutators `add_stimulus_<kind>s` — build from input, merge into the
|
|
99
|
+
# per-kind collection ivar. Methods defined: `add_stimulus_controllers`,
|
|
100
|
+
# `add_stimulus_actions`, `add_stimulus_targets`, `add_stimulus_outlets`,
|
|
101
|
+
# `add_stimulus_values`, `add_stimulus_params`, `add_stimulus_classes`.
|
|
102
|
+
Stimulus::PRIMITIVES.each do |primitive|
|
|
103
|
+
define_method(mutator_method(primitive)) do |input|
|
|
104
|
+
added = send(primitive.key, *Array.wrap(input))
|
|
105
|
+
existing = instance_variable_get(collection_ivar(primitive))
|
|
106
|
+
instance_variable_set(collection_ivar(primitive), existing ? existing.merge(added) : added)
|
|
196
107
|
end
|
|
197
108
|
end
|
|
198
109
|
|
|
199
|
-
def
|
|
200
|
-
s_targets = stimulus_targets(*Array.wrap(targets))
|
|
201
|
-
@stimulus_targets_collection = if @stimulus_targets_collection
|
|
202
|
-
@stimulus_targets_collection.merge(s_targets)
|
|
203
|
-
else
|
|
204
|
-
s_targets
|
|
205
|
-
end
|
|
206
|
-
end
|
|
110
|
+
def stimulus_scoped_event(event) = self.class.stimulus_scoped_event(event)
|
|
207
111
|
|
|
208
|
-
def
|
|
209
|
-
s_outlets = stimulus_outlets(*Array.wrap(outlets))
|
|
210
|
-
@stimulus_outlets_collection = if @stimulus_outlets_collection
|
|
211
|
-
@stimulus_outlets_collection.merge(s_outlets)
|
|
212
|
-
else
|
|
213
|
-
s_outlets
|
|
214
|
-
end
|
|
215
|
-
end
|
|
216
|
-
|
|
217
|
-
def add_stimulus_values(values)
|
|
218
|
-
s_values = stimulus_values(values)
|
|
219
|
-
@stimulus_values_collection = if @stimulus_values_collection
|
|
220
|
-
@stimulus_values_collection.merge(s_values)
|
|
221
|
-
else
|
|
222
|
-
s_values
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
def add_stimulus_classes(named_classes)
|
|
227
|
-
classes = stimulus_classes(named_classes)
|
|
228
|
-
@stimulus_classes_collection = if @stimulus_classes_collection
|
|
229
|
-
@stimulus_classes_collection.merge(classes)
|
|
230
|
-
else
|
|
231
|
-
classes
|
|
232
|
-
end
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
# Stimulus events name for this component
|
|
236
|
-
def stimulus_scoped_event(event)
|
|
237
|
-
self.class.stimulus_scoped_event(event)
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
def stimulus_scoped_event_on_window(event)
|
|
241
|
-
self.class.stimulus_scoped_event_on_window(event)
|
|
242
|
-
end
|
|
112
|
+
def stimulus_scoped_event_on_window(event) = self.class.stimulus_scoped_event_on_window(event)
|
|
243
113
|
|
|
244
114
|
private
|
|
245
115
|
|
|
@@ -247,7 +117,8 @@ module Vident
|
|
|
247
117
|
StimulusController.new(implied_controller: implied_controller_path)
|
|
248
118
|
end
|
|
249
119
|
|
|
250
|
-
#
|
|
120
|
+
# The first registered controller path becomes the implied controller for
|
|
121
|
+
# unqualified DSL entries (e.g. `actions :click` → `implied#click`).
|
|
251
122
|
def implied_controller_path
|
|
252
123
|
return @implied_controller_path if defined?(@implied_controller_path)
|
|
253
124
|
path = Array.wrap(@stimulus_controllers).first
|
|
@@ -2,139 +2,118 @@
|
|
|
2
2
|
|
|
3
3
|
module Vident
|
|
4
4
|
class StimulusBuilder
|
|
5
|
+
# Primitives the DSL block tracks. Controllers are set via the component's
|
|
6
|
+
# `stimulus_controllers:` prop, not the DSL, so they're skipped here.
|
|
7
|
+
# Storage shape per primitive is an Array for positional kinds (actions,
|
|
8
|
+
# targets) and a Hash for keyed kinds (values, params, classes, outlets).
|
|
9
|
+
DSL_PRIMITIVES = Stimulus::PRIMITIVES.reject { |primitive| primitive.name == :controllers }.freeze
|
|
10
|
+
|
|
5
11
|
def initialize
|
|
6
|
-
@
|
|
7
|
-
@targets = []
|
|
8
|
-
@values = {}
|
|
12
|
+
@entries = DSL_PRIMITIVES.to_h { |primitive| [primitive.name, primitive.keyed? ? {} : []] }
|
|
9
13
|
@values_from_props = []
|
|
10
|
-
@classes = {}
|
|
11
|
-
@outlets = {}
|
|
12
14
|
end
|
|
13
15
|
|
|
14
|
-
def merge_with(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
@
|
|
16
|
+
def merge_with(other)
|
|
17
|
+
DSL_PRIMITIVES.each do |primitive|
|
|
18
|
+
mine = @entries[primitive.name]
|
|
19
|
+
theirs = other.entries_for(primitive.name)
|
|
20
|
+
primitive.keyed? ? mine.merge!(theirs) : mine.concat(theirs)
|
|
21
|
+
end
|
|
22
|
+
@values_from_props.concat(other.values_from_props_list)
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def actions(*names)
|
|
27
|
+
@entries[:actions].concat(names)
|
|
21
28
|
self
|
|
22
29
|
end
|
|
23
30
|
|
|
24
|
-
def
|
|
25
|
-
@
|
|
31
|
+
def targets(*names)
|
|
32
|
+
@entries[:targets].concat(names)
|
|
26
33
|
self
|
|
27
34
|
end
|
|
28
35
|
|
|
29
|
-
def
|
|
30
|
-
@
|
|
36
|
+
def values(**hash)
|
|
37
|
+
@entries[:values].merge!(hash) unless hash.empty?
|
|
31
38
|
self
|
|
32
39
|
end
|
|
33
40
|
|
|
34
|
-
def
|
|
35
|
-
@
|
|
41
|
+
def params(**hash)
|
|
42
|
+
@entries[:params].merge!(hash) unless hash.empty?
|
|
36
43
|
self
|
|
37
44
|
end
|
|
38
45
|
|
|
39
|
-
def
|
|
40
|
-
@
|
|
46
|
+
def classes(**hash)
|
|
47
|
+
@entries[:classes].merge!(hash) unless hash.empty?
|
|
41
48
|
self
|
|
42
49
|
end
|
|
43
50
|
|
|
44
|
-
def
|
|
45
|
-
@
|
|
51
|
+
def values_from_props(*names)
|
|
52
|
+
@values_from_props.concat(names)
|
|
46
53
|
self
|
|
47
54
|
end
|
|
48
55
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
# `outlets({"admin--users" => ".sel"})` accepts a positional Hash for
|
|
57
|
+
# identifiers that can't be Ruby kwarg keys (contain `--`).
|
|
58
|
+
def outlets(positional = nil, **hash)
|
|
59
|
+
bucket = @entries[:outlets]
|
|
60
|
+
bucket.merge!(positional) if positional.is_a?(Hash)
|
|
61
|
+
bucket.merge!(hash) unless hash.empty?
|
|
52
62
|
self
|
|
53
63
|
end
|
|
54
64
|
|
|
55
65
|
def to_attributes(component_instance)
|
|
56
66
|
attrs = {}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
67
|
+
DSL_PRIMITIVES.each do |primitive|
|
|
68
|
+
entries = @entries[primitive.name]
|
|
69
|
+
next if entries.empty?
|
|
70
|
+
attrs[primitive.key] = resolve_entries(primitive, entries, component_instance)
|
|
71
|
+
end
|
|
60
72
|
attrs[:stimulus_values_from_props] = @values_from_props.dup unless @values_from_props.empty?
|
|
61
|
-
attrs[:stimulus_classes] = resolve_hash_classes_filtering_nil(@classes, component_instance) unless @classes.empty?
|
|
62
|
-
attrs[:stimulus_outlets] = @outlets.dup unless @outlets.empty?
|
|
63
73
|
attrs
|
|
64
74
|
end
|
|
65
75
|
|
|
66
|
-
def to_hash(component_instance)
|
|
67
|
-
to_attributes(component_instance)
|
|
68
|
-
end
|
|
76
|
+
def to_hash(component_instance) = to_attributes(component_instance)
|
|
69
77
|
alias_method :to_h, :to_hash
|
|
70
78
|
|
|
71
79
|
protected
|
|
72
80
|
|
|
73
|
-
def
|
|
74
|
-
@actions
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def targets_list
|
|
78
|
-
@targets
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def values_hash
|
|
82
|
-
@values
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def values_from_props_list
|
|
86
|
-
@values_from_props
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def classes_hash
|
|
90
|
-
@classes
|
|
91
|
-
end
|
|
81
|
+
def entries_for(name) = @entries[name]
|
|
92
82
|
|
|
93
|
-
def
|
|
94
|
-
@outlets
|
|
95
|
-
end
|
|
83
|
+
def values_from_props_list = @values_from_props
|
|
96
84
|
|
|
97
85
|
private
|
|
98
86
|
|
|
99
|
-
#
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
result << value unless value.nil?
|
|
110
|
-
end
|
|
87
|
+
# Outlets don't support procs — static merge only. The other keyed kinds
|
|
88
|
+
# and the positional (Array-shaped) kinds resolve procs in the component
|
|
89
|
+
# instance and drop nil results.
|
|
90
|
+
def resolve_entries(primitive, entries, component_instance)
|
|
91
|
+
return entries.dup if primitive.name == :outlets
|
|
92
|
+
|
|
93
|
+
if primitive.keyed?
|
|
94
|
+
resolve_hash_filtering_nil(entries, component_instance)
|
|
95
|
+
else
|
|
96
|
+
resolve_array_filtering_nil(entries, component_instance)
|
|
111
97
|
end
|
|
112
|
-
result
|
|
113
98
|
end
|
|
114
99
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
100
|
+
def resolve_array_filtering_nil(array, component_instance)
|
|
101
|
+
array.each_with_object([]) do |value, out|
|
|
102
|
+
resolved = callable?(value) ? component_instance.instance_exec(&value) : value
|
|
103
|
+
out << resolved unless resolved.nil?
|
|
104
|
+
end
|
|
118
105
|
end
|
|
119
106
|
|
|
120
|
-
#
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
result[key] = resolved_value unless resolved_value.nil?
|
|
128
|
-
else
|
|
129
|
-
# Exclude static nil values (nil is not valid for classes)
|
|
130
|
-
result[key] = value unless value.nil?
|
|
131
|
-
end
|
|
107
|
+
# Dropping nil matters because Stimulus's Boolean value parser reads an
|
|
108
|
+
# empty data attribute as `true` — so `-> { flag? || nil }` would silently
|
|
109
|
+
# flip a Boolean value on. Omitting the entry keeps the attribute off.
|
|
110
|
+
def resolve_hash_filtering_nil(hash, component_instance)
|
|
111
|
+
hash.each_with_object({}) do |(key, value), out|
|
|
112
|
+
resolved = callable?(value) ? component_instance.instance_exec(&value) : value
|
|
113
|
+
out[key] = resolved unless resolved.nil?
|
|
132
114
|
end
|
|
133
|
-
result
|
|
134
115
|
end
|
|
135
116
|
|
|
136
|
-
def callable?(value)
|
|
137
|
-
value.respond_to?(:call)
|
|
138
|
-
end
|
|
117
|
+
def callable?(value) = value.respond_to?(:call)
|
|
139
118
|
end
|
|
140
119
|
end
|
|
@@ -4,17 +4,11 @@ module Vident
|
|
|
4
4
|
class StimulusClass < StimulusAttributeBase
|
|
5
5
|
attr_reader :controller, :class_name, :css_classes
|
|
6
6
|
|
|
7
|
-
def to_s
|
|
8
|
-
@css_classes.join(" ")
|
|
9
|
-
end
|
|
7
|
+
def to_s = @css_classes.join(" ")
|
|
10
8
|
|
|
11
|
-
def data_attribute_name
|
|
12
|
-
"#{@controller}-#{@class_name}-class"
|
|
13
|
-
end
|
|
9
|
+
def data_attribute_name = "#{@controller}-#{@class_name}-class"
|
|
14
10
|
|
|
15
|
-
def data_attribute_value
|
|
16
|
-
to_s
|
|
17
|
-
end
|
|
11
|
+
def data_attribute_value = to_s
|
|
18
12
|
|
|
19
13
|
private
|
|
20
14
|
|
|
@@ -15,21 +15,13 @@ module Vident
|
|
|
15
15
|
raise NoMethodError, "Subclasses must implement to_h"
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
def to_a
|
|
19
|
-
@items.dup
|
|
20
|
-
end
|
|
18
|
+
def to_a = @items.dup
|
|
21
19
|
|
|
22
|
-
def to_hash
|
|
23
|
-
to_h
|
|
24
|
-
end
|
|
20
|
+
def to_hash = to_h
|
|
25
21
|
|
|
26
|
-
def empty?
|
|
27
|
-
@items.empty?
|
|
28
|
-
end
|
|
22
|
+
def empty? = @items.empty?
|
|
29
23
|
|
|
30
|
-
def any?
|
|
31
|
-
!empty?
|
|
32
|
-
end
|
|
24
|
+
def any? = !empty?
|
|
33
25
|
|
|
34
26
|
def merge(*other_collections)
|
|
35
27
|
merged = self.class.new
|
|
@@ -6,10 +6,10 @@ module Vident
|
|
|
6
6
|
|
|
7
7
|
include StimulusAttributes
|
|
8
8
|
|
|
9
|
-
#
|
|
10
|
-
|
|
9
|
+
# Thin back-compat alias; see `Vident::StimulusAttributeBase.stimulize_path`
|
|
10
|
+
# for the canonical implementation.
|
|
11
11
|
def stimulus_identifier_from_path(path)
|
|
12
|
-
|
|
12
|
+
StimulusAttributeBase.stimulize_path(path)
|
|
13
13
|
end
|
|
14
14
|
module_function :stimulus_identifier_from_path
|
|
15
15
|
|
|
@@ -26,7 +26,7 @@ module Vident
|
|
|
26
26
|
def stimulus_identifier_path = name&.underscore || "anonymous_component"
|
|
27
27
|
|
|
28
28
|
# Stimulus controller identifier
|
|
29
|
-
def stimulus_identifier =
|
|
29
|
+
def stimulus_identifier = StimulusComponent.stimulus_identifier_from_path(stimulus_identifier_path)
|
|
30
30
|
|
|
31
31
|
# The "name" of the component from its class name and namespace. This is used to generate an HTML class name
|
|
32
32
|
# that can helps identify the component type in the DOM or for styling purposes.
|
|
@@ -48,12 +48,13 @@ module Vident
|
|
|
48
48
|
[]
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
|
-
prop :stimulus_actions, _Array(_Union(String, Symbol, Array, Hash, StimulusAction, StimulusActionCollection)), default: -> { [] }
|
|
51
|
+
prop :stimulus_actions, _Array(_Union(String, Symbol, Array, Hash, StimulusAction, StimulusAction::Descriptor, StimulusActionCollection)), default: -> { [] }
|
|
52
52
|
prop :stimulus_targets, _Array(_Union(String, Symbol, Array, Hash, StimulusTarget, StimulusTargetCollection)), default: -> { [] }
|
|
53
53
|
prop :stimulus_outlets, _Array(_Union(String, Symbol, StimulusOutlet, StimulusOutletCollection)), default: -> { [] }
|
|
54
54
|
prop :stimulus_outlet_host, _Nilable(Vident::Component) # A component that will host this component as an outlet
|
|
55
|
-
prop :stimulus_values, _Union(_Hash(Symbol, _Any), StimulusValue, StimulusValueCollection), default: -> { {} } # TODO: instead of _Any, is it _Interface(:to_s)?
|
|
56
|
-
prop :
|
|
55
|
+
prop :stimulus_values, _Union(_Hash(Symbol, _Any), Array, StimulusValue, StimulusValueCollection), default: -> { {} } # TODO: instead of _Any, is it _Interface(:to_s)?
|
|
56
|
+
prop :stimulus_params, _Union(_Hash(Symbol, _Any), Array, StimulusParam, StimulusParamCollection), default: -> { {} }
|
|
57
|
+
prop :stimulus_classes, _Union(_Hash(Symbol, String), Array, StimulusClass, StimulusClassCollection), default: -> { {} }
|
|
57
58
|
end
|
|
58
59
|
|
|
59
60
|
# If connecting an outlet to this specific component instance, use this ID
|
|
@@ -4,38 +4,35 @@ module Vident
|
|
|
4
4
|
class StimulusController < StimulusAttributeBase
|
|
5
5
|
attr_reader :path, :name
|
|
6
6
|
|
|
7
|
-
def to_s
|
|
8
|
-
name
|
|
9
|
-
end
|
|
7
|
+
def to_s = name
|
|
10
8
|
|
|
11
|
-
def data_attribute_name
|
|
12
|
-
"controller"
|
|
13
|
-
end
|
|
9
|
+
def data_attribute_name = "controller"
|
|
14
10
|
|
|
15
|
-
def data_attribute_value
|
|
16
|
-
name
|
|
17
|
-
end
|
|
11
|
+
def data_attribute_value = name
|
|
18
12
|
|
|
19
13
|
private
|
|
20
14
|
|
|
15
|
+
# `@implied_controller` on this class is a raw path String (not a
|
|
16
|
+
# StimulusController instance as on the base), so the base class's
|
|
17
|
+
# `.path` / `.name` accessors don't apply and we override.
|
|
21
18
|
def implied_controller_path
|
|
19
|
+
raise ArgumentError, "implied_controller is required to get implied controller path" unless @implied_controller
|
|
22
20
|
@implied_controller
|
|
23
21
|
end
|
|
24
22
|
|
|
25
23
|
def implied_controller_name
|
|
24
|
+
raise ArgumentError, "implied_controller is required to get implied controller name" unless @implied_controller
|
|
26
25
|
stimulize_path(@implied_controller)
|
|
27
26
|
end
|
|
28
27
|
|
|
29
28
|
def parse_arguments(*args)
|
|
30
29
|
case args.size
|
|
31
30
|
when 0
|
|
32
|
-
# No arguments: use implied controller path
|
|
33
31
|
@path = implied_controller_path
|
|
34
32
|
@name = implied_controller_name
|
|
35
33
|
when 1
|
|
36
|
-
|
|
37
|
-
@
|
|
38
|
-
@name = stimulize_path(args[0])
|
|
34
|
+
@path = args[0].to_s
|
|
35
|
+
@name = stimulize_path(@path)
|
|
39
36
|
else
|
|
40
37
|
raise ArgumentError, "Invalid number of arguments: #{args.size}. Expected 0 or 1 argument."
|
|
41
38
|
end
|