vident 1.0.2 → 2.0.1
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 +50 -0
- data/README.md +45 -17
- data/lib/vident/caching.rb +4 -110
- data/lib/vident/capabilities/caching.rb +98 -0
- data/lib/vident/capabilities/child_element_rendering.rb +92 -0
- data/lib/vident/capabilities/class_list_building.rb +23 -0
- data/lib/vident/capabilities/declarable.rb +39 -0
- data/lib/vident/capabilities/identifiable.rb +54 -0
- data/lib/vident/capabilities/inspectable.rb +17 -0
- data/lib/vident/capabilities/root_element_rendering.rb +31 -0
- data/lib/vident/capabilities/stimulus_data_emitting.rb +98 -0
- data/lib/vident/capabilities/stimulus_declaring.rb +79 -0
- data/lib/vident/capabilities/stimulus_draft.rb +51 -0
- data/lib/vident/capabilities/stimulus_mutation.rb +60 -0
- data/lib/vident/capabilities/stimulus_parsing.rb +144 -0
- data/lib/vident/capabilities/tailwind.rb +18 -0
- data/lib/vident/component.rb +14 -76
- data/lib/vident/engine.rb +6 -5
- data/lib/vident/error.rb +16 -0
- data/lib/{vident2 → vident}/internals/action_builder.rb +18 -22
- data/lib/vident/internals/attribute_writer.rb +17 -0
- data/lib/{vident2 → vident}/internals/class_list_builder.rb +5 -22
- data/lib/vident/internals/declaration.rb +13 -0
- data/lib/{vident2 → vident}/internals/declarations.rb +6 -18
- data/lib/{vident2 → vident}/internals/draft.rb +3 -16
- data/lib/{vident2 → vident}/internals/dsl.rb +6 -32
- data/lib/vident/internals/plan.rb +9 -0
- data/lib/vident/internals/registry.rb +37 -0
- data/lib/{vident2 → vident}/internals/resolver.rb +101 -91
- data/lib/{vident2 → vident}/internals/target_builder.rb +1 -7
- data/lib/vident/stable_id.rb +3 -3
- data/lib/{vident2 → vident}/stimulus/action.rb +11 -24
- data/lib/vident/stimulus/base.rb +26 -0
- data/lib/{vident2 → vident}/stimulus/class_map.rb +6 -18
- data/lib/{vident2 → vident}/stimulus/collection.rb +6 -8
- data/lib/vident/stimulus/combinable.rb +30 -0
- data/lib/vident/stimulus/controller.rb +45 -0
- data/lib/vident/stimulus/naming.rb +9 -9
- data/lib/vident/stimulus/null.rb +7 -0
- data/lib/{vident2 → vident}/stimulus/outlet.rb +12 -32
- data/lib/{vident2 → vident}/stimulus/param.rb +5 -11
- data/lib/{vident2 → vident}/stimulus/target.rb +5 -14
- data/lib/vident/stimulus/value.rb +57 -0
- data/lib/vident/stimulus_null.rb +4 -8
- data/lib/vident/tailwind.rb +4 -17
- data/lib/vident/types.rb +28 -0
- data/lib/vident/version.rb +1 -6
- data/lib/vident.rb +46 -36
- data/skills/vident/SKILL.md +122 -19
- data/skills/vident/api-reference.md +259 -115
- data/skills/vident/examples.md +23 -10
- metadata +38 -60
- data/lib/vident/child_element_helper.rb +0 -64
- data/lib/vident/class_list_builder.rb +0 -112
- data/lib/vident/component_attribute_resolver.rb +0 -106
- data/lib/vident/component_class_lists.rb +0 -37
- data/lib/vident/stimulus/primitive.rb +0 -38
- data/lib/vident/stimulus.rb +0 -31
- data/lib/vident/stimulus_action.rb +0 -133
- data/lib/vident/stimulus_action_collection.rb +0 -11
- data/lib/vident/stimulus_attribute_base.rb +0 -67
- data/lib/vident/stimulus_attributes.rb +0 -129
- data/lib/vident/stimulus_builder.rb +0 -136
- data/lib/vident/stimulus_class.rb +0 -59
- data/lib/vident/stimulus_class_collection.rb +0 -11
- data/lib/vident/stimulus_collection_base.rb +0 -51
- data/lib/vident/stimulus_component.rb +0 -75
- data/lib/vident/stimulus_controller.rb +0 -41
- data/lib/vident/stimulus_controller_collection.rb +0 -14
- data/lib/vident/stimulus_data_attribute_builder.rb +0 -32
- data/lib/vident/stimulus_helper.rb +0 -66
- data/lib/vident/stimulus_outlet.rb +0 -90
- data/lib/vident/stimulus_outlet_collection.rb +0 -11
- data/lib/vident/stimulus_param.rb +0 -42
- data/lib/vident/stimulus_param_collection.rb +0 -11
- data/lib/vident/stimulus_target.rb +0 -47
- data/lib/vident/stimulus_target_collection.rb +0 -18
- data/lib/vident/stimulus_value.rb +0 -39
- data/lib/vident/stimulus_value_collection.rb +0 -11
- data/lib/vident2/caching.rb +0 -93
- data/lib/vident2/component.rb +0 -538
- data/lib/vident2/engine.rb +0 -18
- data/lib/vident2/error.rb +0 -30
- data/lib/vident2/internals/attribute_writer.rb +0 -22
- data/lib/vident2/internals/declaration.rb +0 -17
- data/lib/vident2/internals/plan.rb +0 -12
- data/lib/vident2/internals/registry.rb +0 -41
- data/lib/vident2/phlex/html.rb +0 -84
- data/lib/vident2/phlex.rb +0 -9
- data/lib/vident2/stimulus/controller.rb +0 -59
- data/lib/vident2/stimulus/naming.rb +0 -26
- data/lib/vident2/stimulus/null.rb +0 -16
- data/lib/vident2/stimulus/value.rb +0 -77
- data/lib/vident2/tailwind.rb +0 -19
- data/lib/vident2/version.rb +0 -5
- data/lib/vident2/view_component/base.rb +0 -124
- data/lib/vident2/view_component.rb +0 -9
- data/lib/vident2.rb +0 -50
data/lib/vident2/component.rb
DELETED
|
@@ -1,538 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "set"
|
|
4
|
-
|
|
5
|
-
require_relative "error"
|
|
6
|
-
require_relative "internals/declarations"
|
|
7
|
-
require_relative "internals/dsl"
|
|
8
|
-
require_relative "internals/registry"
|
|
9
|
-
require_relative "internals/draft"
|
|
10
|
-
require_relative "internals/plan"
|
|
11
|
-
require_relative "internals/resolver"
|
|
12
|
-
require_relative "internals/attribute_writer"
|
|
13
|
-
require_relative "internals/class_list_builder"
|
|
14
|
-
require_relative "stimulus/collection"
|
|
15
|
-
require_relative "tailwind"
|
|
16
|
-
|
|
17
|
-
module Vident2
|
|
18
|
-
# Composition root for Vident 2.0 components: props, DSL receiver,
|
|
19
|
-
# singular/plural parsers, add_stimulus_* mutators, and the render
|
|
20
|
-
# pipeline glue.
|
|
21
|
-
module Component
|
|
22
|
-
extend ActiveSupport::Concern
|
|
23
|
-
|
|
24
|
-
include ::Vident2::Tailwind
|
|
25
|
-
|
|
26
|
-
included do
|
|
27
|
-
extend Literal::Properties
|
|
28
|
-
|
|
29
|
-
prop :element_tag, Symbol, default: :div
|
|
30
|
-
prop :id, _Nilable(String)
|
|
31
|
-
prop :classes, _Union(String, _Array(String)), default: -> { [] }
|
|
32
|
-
prop :html_options, Hash, default: -> { {} }
|
|
33
|
-
|
|
34
|
-
# Stimulus input props. Resolver folds these into the Draft at init.
|
|
35
|
-
# `stimulus_controllers:` APPENDS to the implied controller (which
|
|
36
|
-
# seeds first unless `no_stimulus_controller`).
|
|
37
|
-
prop :stimulus_controllers,
|
|
38
|
-
_Array(_Union(String, Symbol, ::Vident2::Stimulus::Controller)),
|
|
39
|
-
default: -> { [] }
|
|
40
|
-
prop :stimulus_actions,
|
|
41
|
-
_Array(_Union(String, Symbol, Array, Hash, ::Vident2::Stimulus::Action)),
|
|
42
|
-
default: -> { [] }
|
|
43
|
-
prop :stimulus_targets,
|
|
44
|
-
_Array(_Union(String, Symbol, Array, ::Vident2::Stimulus::Target)),
|
|
45
|
-
default: -> { [] }
|
|
46
|
-
prop :stimulus_outlets,
|
|
47
|
-
_Array(_Union(String, Symbol, Array, ::Vident2::Stimulus::Outlet)),
|
|
48
|
-
default: -> { [] }
|
|
49
|
-
prop :stimulus_outlet_host, _Nilable(::Vident2::Component)
|
|
50
|
-
prop :stimulus_values,
|
|
51
|
-
_Union(_Hash(Symbol, _Any), Array, ::Vident2::Stimulus::Value),
|
|
52
|
-
default: -> { {} }
|
|
53
|
-
prop :stimulus_params,
|
|
54
|
-
_Union(_Hash(Symbol, _Any), Array, ::Vident2::Stimulus::Param),
|
|
55
|
-
default: -> { {} }
|
|
56
|
-
prop :stimulus_classes,
|
|
57
|
-
_Union(_Hash(Symbol, _Any), Array, ::Vident2::Stimulus::ClassMap),
|
|
58
|
-
default: -> { {} }
|
|
59
|
-
|
|
60
|
-
# Eager inheritance: subclasses copy parent's frozen Declarations
|
|
61
|
-
# (see `inherited` below).
|
|
62
|
-
@__vident2_declarations = ::Vident2::Internals::Declarations.empty
|
|
63
|
-
@__vident2_no_stimulus_controller = false
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
class_methods do
|
|
67
|
-
def prop_names
|
|
68
|
-
literal_properties.properties_index.keys.map(&:to_sym)
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
# `Admin::UserCardComponent` → `admin/user_card_component`.
|
|
72
|
-
# Anonymous classes return a stable placeholder.
|
|
73
|
-
def stimulus_identifier_path
|
|
74
|
-
name&.underscore || "anonymous_component"
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# The `data-controller` / root-class form, e.g. `admin--user-card-component`.
|
|
78
|
-
def stimulus_identifier
|
|
79
|
-
stimulus_identifier_path.split("/").map(&:dasherize).join("--")
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def component_name
|
|
83
|
-
@component_name ||= stimulus_identifier
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
# Frozen DSL aggregate (own + inherited). Always non-nil.
|
|
87
|
-
def declarations
|
|
88
|
-
@__vident2_declarations ||= ::Vident2::Internals::Declarations.empty
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# Suppresses the implied controller. A `stimulus do` block with
|
|
92
|
-
# entries after this call raises `DeclarationError`.
|
|
93
|
-
def no_stimulus_controller
|
|
94
|
-
if declarations.any?
|
|
95
|
-
raise ::Vident2::DeclarationError,
|
|
96
|
-
"#{name || "anonymous component"} called `no_stimulus_controller` after " \
|
|
97
|
-
"`stimulus do` already recorded DSL entries. Declare `no_stimulus_controller` " \
|
|
98
|
-
"before any `stimulus do` block."
|
|
99
|
-
end
|
|
100
|
-
@__vident2_no_stimulus_controller = true
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def stimulus_controller?
|
|
104
|
-
!@__vident2_no_stimulus_controller
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# `stimulus do ... end` block receiver. Second+ calls append
|
|
108
|
-
# (positional) or last-write-wins (keyed).
|
|
109
|
-
def stimulus(&block)
|
|
110
|
-
call_site = caller_locations(1, 1)&.first
|
|
111
|
-
dsl = ::Vident2::Internals::DSL.new(caller_location: call_site)
|
|
112
|
-
dsl.instance_eval(&block) if block
|
|
113
|
-
fresh = dsl.to_declarations
|
|
114
|
-
|
|
115
|
-
if !stimulus_controller? && fresh.any?
|
|
116
|
-
location = call_site ? " at #{call_site.path}:#{call_site.lineno}" : ""
|
|
117
|
-
raise ::Vident2::DeclarationError,
|
|
118
|
-
"#{name || "anonymous component"} declared `no_stimulus_controller` but `stimulus do` emitted DSL entries#{location}. " \
|
|
119
|
-
"A class with no implied controller cannot route DSL entries; drop the `stimulus do` block or remove `no_stimulus_controller`."
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
@__vident2_declarations = declarations.merge(fresh).freeze
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
# Component-scoped Stimulus event (Symbol, usable directly in `action` DSL).
|
|
126
|
-
def stimulus_scoped_event(event)
|
|
127
|
-
:"#{component_name}:#{event.to_s.camelize(:lower)}"
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def stimulus_scoped_event_on_window(event)
|
|
131
|
-
:"#{component_name}:#{event.to_s.camelize(:lower)}@window"
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
# @api private — called by the Ruby VM on subclass definition.
|
|
135
|
-
def inherited(subclass)
|
|
136
|
-
super
|
|
137
|
-
subclass.instance_variable_set(:@__vident2_declarations, declarations)
|
|
138
|
-
subclass.instance_variable_set(
|
|
139
|
-
:@__vident2_no_stimulus_controller,
|
|
140
|
-
instance_variable_get(:@__vident2_no_stimulus_controller) || false
|
|
141
|
-
)
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
def prop_names = self.class.prop_names
|
|
146
|
-
def component_name = self.class.component_name
|
|
147
|
-
def stimulus_identifier = self.class.stimulus_identifier
|
|
148
|
-
|
|
149
|
-
private def default_controller_path = self.class.stimulus_identifier_path
|
|
150
|
-
|
|
151
|
-
def stimulus_scoped_event(event) = self.class.stimulus_scoped_event(event)
|
|
152
|
-
def stimulus_scoped_event_on_window(event) = self.class.stimulus_scoped_event_on_window(event)
|
|
153
|
-
|
|
154
|
-
# Auto-id: `<component-name>-<stable-id>`. `.presence` is intentional
|
|
155
|
-
# — blank string falls through to auto-generation.
|
|
156
|
-
def id
|
|
157
|
-
@id.presence || random_id
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
# Auto-generated id, independent of the `id:` prop. Useful for ARIA
|
|
161
|
-
# references that need to be stable per-instance.
|
|
162
|
-
def random_id
|
|
163
|
-
@__vident2_auto_id ||= "#{component_name}-#{::Vident2::StableId.next_id_in_sequence}"
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
# Stable `[identifier, "#<id>"]` pair for connecting an outlet to
|
|
167
|
-
# this instance.
|
|
168
|
-
def outlet_id
|
|
169
|
-
@outlet_id ||= [stimulus_identifier, "##{id}"]
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
# Fresh instance with current props merged with overrides.
|
|
173
|
-
def clone(overrides = {})
|
|
174
|
-
self.class.new(**to_h.merge(overrides))
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
# Custom format kept for tooling / specs that regex-match output.
|
|
178
|
-
def inspect(klass_name = "Component")
|
|
179
|
-
attr_text = to_h.map { |k, v| "#{k}=#{v.inspect}" }.join(", ")
|
|
180
|
-
"#<#{self.class.name}<Vident::#{klass_name}> #{attr_text}>"
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
# Memoised `root_element_attributes`: the user's override runs exactly
|
|
184
|
-
# once (across Resolver + renderer reads).
|
|
185
|
-
private def resolved_root_element_attributes
|
|
186
|
-
return @__vident2_rea if defined?(@__vident2_rea)
|
|
187
|
-
value = root_element_attributes
|
|
188
|
-
@__vident2_rea = value.is_a?(Hash) ? value : {}
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
# User override: extra attrs for the root. `stimulus_*:` keys APPEND
|
|
192
|
-
# into the Draft (same as props).
|
|
193
|
-
def root_element_attributes = {}
|
|
194
|
-
|
|
195
|
-
# User override: instance-level extra classes for the root (one tier
|
|
196
|
-
# of ClassListBuilder's cascade). Return nil for no contribution.
|
|
197
|
-
def root_element_classes
|
|
198
|
-
nil
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
# User hook: runs after the Draft is built but before seal.
|
|
202
|
-
def after_component_initialize
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
# Singular parsers return a pre-built value object; pre-built input
|
|
206
|
-
# passes through unchanged.
|
|
207
|
-
SINGULAR_PARSERS = {
|
|
208
|
-
controllers: :stimulus_controller,
|
|
209
|
-
actions: :stimulus_action,
|
|
210
|
-
targets: :stimulus_target,
|
|
211
|
-
outlets: :stimulus_outlet,
|
|
212
|
-
values: :stimulus_value,
|
|
213
|
-
params: :stimulus_param,
|
|
214
|
-
class_maps: :stimulus_class
|
|
215
|
-
}.freeze
|
|
216
|
-
|
|
217
|
-
SINGULAR_PARSERS.each do |kind_name, method_name|
|
|
218
|
-
kind = ::Vident2::Internals::Registry.fetch(kind_name)
|
|
219
|
-
define_method(method_name) do |*args|
|
|
220
|
-
return args.first if args.length == 1 && args.first.is_a?(kind.value_class)
|
|
221
|
-
kind.value_class.parse(*args, implied: implied_controller, component_id: id)
|
|
222
|
-
end
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
# Plural parsers return a Collection (exposes `#to_h` for `data:`
|
|
226
|
-
# splatting). Inputs: Symbol / String / Array (= singular parser's
|
|
227
|
-
# arg tuple) / Hash (keyed: one pair each) / pre-built Value or
|
|
228
|
-
# Collection (pass-through).
|
|
229
|
-
PLURAL_PARSERS = {
|
|
230
|
-
controllers: :stimulus_controllers,
|
|
231
|
-
actions: :stimulus_actions,
|
|
232
|
-
targets: :stimulus_targets,
|
|
233
|
-
outlets: :stimulus_outlets,
|
|
234
|
-
values: :stimulus_values,
|
|
235
|
-
params: :stimulus_params,
|
|
236
|
-
class_maps: :stimulus_classes
|
|
237
|
-
}.freeze
|
|
238
|
-
|
|
239
|
-
PLURAL_PARSERS.each do |kind_name, method_name|
|
|
240
|
-
kind = ::Vident2::Internals::Registry.fetch(kind_name)
|
|
241
|
-
define_method(method_name) do |*args|
|
|
242
|
-
return ::Vident2::Stimulus::Collection.new(kind: kind, items: []) if args.empty? || args.all?(&:nil?)
|
|
243
|
-
return args.first if args.length == 1 && args.first.is_a?(::Vident2::Stimulus::Collection)
|
|
244
|
-
|
|
245
|
-
items = []
|
|
246
|
-
args.each do |arg|
|
|
247
|
-
case arg
|
|
248
|
-
when kind.value_class
|
|
249
|
-
items << arg
|
|
250
|
-
when ::Vident2::Stimulus::Collection
|
|
251
|
-
items.concat(arg.items)
|
|
252
|
-
when Hash
|
|
253
|
-
if kind.keyed
|
|
254
|
-
arg.each { |name, val| items << kind.value_class.parse(name, val, implied: implied_controller, component_id: id) }
|
|
255
|
-
else
|
|
256
|
-
items << kind.value_class.parse(arg, implied: implied_controller, component_id: id)
|
|
257
|
-
end
|
|
258
|
-
when Array
|
|
259
|
-
items << kind.value_class.parse(*arg, implied: implied_controller, component_id: id)
|
|
260
|
-
else
|
|
261
|
-
items << kind.value_class.parse(arg, implied: implied_controller, component_id: id)
|
|
262
|
-
end
|
|
263
|
-
end
|
|
264
|
-
::Vident2::Stimulus::Collection.new(kind: kind, items: items)
|
|
265
|
-
end
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
# Mutators. One call = one entry: Array input is the singular
|
|
269
|
-
# parser's arg tuple, NOT splatted across multiple mutator calls.
|
|
270
|
-
MUTATOR_METHODS = {
|
|
271
|
-
controllers: :add_stimulus_controllers,
|
|
272
|
-
actions: :add_stimulus_actions,
|
|
273
|
-
targets: :add_stimulus_targets,
|
|
274
|
-
outlets: :add_stimulus_outlets,
|
|
275
|
-
values: :add_stimulus_values,
|
|
276
|
-
params: :add_stimulus_params,
|
|
277
|
-
class_maps: :add_stimulus_classes
|
|
278
|
-
}.freeze
|
|
279
|
-
|
|
280
|
-
# Kind name → `stimulus_<singular>` suffix (used by child_element).
|
|
281
|
-
SINGULAR_NAMES = {
|
|
282
|
-
controllers: :controller,
|
|
283
|
-
actions: :action,
|
|
284
|
-
targets: :target,
|
|
285
|
-
outlets: :outlet,
|
|
286
|
-
values: :value,
|
|
287
|
-
params: :param,
|
|
288
|
-
class_maps: :class
|
|
289
|
-
}.freeze
|
|
290
|
-
|
|
291
|
-
MUTATOR_METHODS.each do |kind_name, method_name|
|
|
292
|
-
kind = ::Vident2::Internals::Registry.fetch(kind_name)
|
|
293
|
-
define_method(method_name) do |input|
|
|
294
|
-
raise_if_sealed!
|
|
295
|
-
values = unwrap_mutator_input(kind, input)
|
|
296
|
-
values.each { |v| @__vident2_draft.public_send(:"add_#{kind.name}", v) if v }
|
|
297
|
-
self
|
|
298
|
-
end
|
|
299
|
-
end
|
|
300
|
-
|
|
301
|
-
# SSR helper: resolved ClassMap entries matching `names` as a
|
|
302
|
-
# space-joined String. Tailwind-merged if available. `""` on miss.
|
|
303
|
-
def class_list_for_stimulus_classes(*names)
|
|
304
|
-
resolve_stimulus_attributes_at_render_time
|
|
305
|
-
plan = seal_draft
|
|
306
|
-
maps = plan.class_maps
|
|
307
|
-
return "" if maps.empty? || names.empty?
|
|
308
|
-
|
|
309
|
-
result = ::Vident2::Internals::ClassListBuilder.call(
|
|
310
|
-
stimulus_classes: maps,
|
|
311
|
-
stimulus_class_names: names,
|
|
312
|
-
tailwind_merger: tailwind_merger
|
|
313
|
-
)
|
|
314
|
-
result || ""
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
# Emit a child element with stimulus_* kwargs folded into data-*
|
|
318
|
-
# attrs. Plural kwargs must be Enumerable. Adapter provides the tag
|
|
319
|
-
# emission (`generate_child_element`).
|
|
320
|
-
def child_element(
|
|
321
|
-
tag_name,
|
|
322
|
-
stimulus_controllers: nil,
|
|
323
|
-
stimulus_targets: nil,
|
|
324
|
-
stimulus_actions: nil,
|
|
325
|
-
stimulus_outlets: nil,
|
|
326
|
-
stimulus_values: nil,
|
|
327
|
-
stimulus_params: nil,
|
|
328
|
-
stimulus_classes: nil,
|
|
329
|
-
stimulus_controller: nil,
|
|
330
|
-
stimulus_target: nil,
|
|
331
|
-
stimulus_action: nil,
|
|
332
|
-
stimulus_outlet: nil,
|
|
333
|
-
stimulus_value: nil,
|
|
334
|
-
stimulus_param: nil,
|
|
335
|
-
stimulus_class: nil,
|
|
336
|
-
**options,
|
|
337
|
-
&block
|
|
338
|
-
)
|
|
339
|
-
inputs = {
|
|
340
|
-
controllers: [stimulus_controllers, stimulus_controller],
|
|
341
|
-
actions: [stimulus_actions, stimulus_action],
|
|
342
|
-
targets: [stimulus_targets, stimulus_target],
|
|
343
|
-
outlets: [stimulus_outlets, stimulus_outlet],
|
|
344
|
-
values: [stimulus_values, stimulus_value],
|
|
345
|
-
params: [stimulus_params, stimulus_param],
|
|
346
|
-
class_maps: [stimulus_classes, stimulus_class]
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
data_attrs = {}
|
|
350
|
-
::Vident2::Internals::Registry.each do |kind|
|
|
351
|
-
plural, singular = inputs.fetch(kind.name)
|
|
352
|
-
child_element_check_plural!(plural, singular, kind)
|
|
353
|
-
coll = child_element_build_collection(kind, plural, singular)
|
|
354
|
-
data_attrs.merge!(coll.to_h) unless coll.empty?
|
|
355
|
-
end
|
|
356
|
-
|
|
357
|
-
generate_child_element(tag_name, data_attrs, options, &block)
|
|
358
|
-
end
|
|
359
|
-
|
|
360
|
-
private
|
|
361
|
-
|
|
362
|
-
# Literal callback after props are assigned. Builds the Draft with
|
|
363
|
-
# STATIC entries; DSL procs defer to render (adapter's
|
|
364
|
-
# `before_template` / `before_render` — `view_context` isn't wired yet).
|
|
365
|
-
def after_initialize
|
|
366
|
-
@__vident2_draft = ::Vident2::Internals::Resolver.call(
|
|
367
|
-
self.class.declarations, self, phase: :static
|
|
368
|
-
)
|
|
369
|
-
@stimulus_outlet_host&.add_stimulus_outlets(self)
|
|
370
|
-
after_component_initialize
|
|
371
|
-
end
|
|
372
|
-
|
|
373
|
-
public
|
|
374
|
-
|
|
375
|
-
# Resolve DSL proc entries deferred at `after_initialize`. Called by
|
|
376
|
-
# the adapter's `before_template` / `before_render`; `seal_draft` and
|
|
377
|
-
# `class_list_for_stimulus_classes` call it as safety nets.
|
|
378
|
-
#
|
|
379
|
-
# Flag set before the guards so a sealed Draft can't trap us in a
|
|
380
|
-
# loop where every subsequent call re-takes the sealed branch.
|
|
381
|
-
def resolve_stimulus_attributes_at_render_time
|
|
382
|
-
return if @__vident2_procs_resolved
|
|
383
|
-
@__vident2_procs_resolved = true
|
|
384
|
-
# Nil = test double. Sealed = someone consumed the Draft already.
|
|
385
|
-
return if @__vident2_draft.nil? || @__vident2_draft.sealed?
|
|
386
|
-
::Vident2::Internals::Resolver.resolve_procs_into(
|
|
387
|
-
@__vident2_draft, self.class.declarations, self
|
|
388
|
-
)
|
|
389
|
-
end
|
|
390
|
-
|
|
391
|
-
private
|
|
392
|
-
|
|
393
|
-
def implied_controller
|
|
394
|
-
@__vident2_implied_controller ||= ::Vident2::Stimulus::Controller.new(
|
|
395
|
-
path: self.class.stimulus_identifier_path,
|
|
396
|
-
name: self.class.stimulus_identifier
|
|
397
|
-
)
|
|
398
|
-
end
|
|
399
|
-
|
|
400
|
-
# Array input is ONE entry — V2 intentionally does not splat Arrays
|
|
401
|
-
# across entries (mirrors the DSL's plural→singular forwarding).
|
|
402
|
-
def unwrap_mutator_input(kind, input)
|
|
403
|
-
return [] if input.nil?
|
|
404
|
-
return [input] if input.is_a?(kind.value_class)
|
|
405
|
-
return input.items if input.is_a?(::Vident2::Stimulus::Collection)
|
|
406
|
-
|
|
407
|
-
if kind.keyed && input.is_a?(Hash)
|
|
408
|
-
return input.map do |name, raw|
|
|
409
|
-
kind.value_class.parse(name, raw, implied: implied_controller, component_id: id)
|
|
410
|
-
end
|
|
411
|
-
end
|
|
412
|
-
|
|
413
|
-
args = input.is_a?(Array) ? input : [input]
|
|
414
|
-
[kind.value_class.parse(*args, implied: implied_controller, component_id: id)]
|
|
415
|
-
end
|
|
416
|
-
|
|
417
|
-
def raise_if_sealed!
|
|
418
|
-
return unless @__vident2_draft&.sealed?
|
|
419
|
-
raise ::Vident2::StateError,
|
|
420
|
-
"cannot modify stimulus attributes after rendering has begun"
|
|
421
|
-
end
|
|
422
|
-
|
|
423
|
-
# Seal Draft and memoise the Plan. Also a safety net for deferred
|
|
424
|
-
# proc resolution when the adapter hook didn't fire.
|
|
425
|
-
def seal_draft
|
|
426
|
-
resolve_stimulus_attributes_at_render_time
|
|
427
|
-
@__vident2_plan ||= @__vident2_draft.seal!
|
|
428
|
-
end
|
|
429
|
-
|
|
430
|
-
# Merged root-element attribute Hash for adapters. `overrides` come
|
|
431
|
-
# from `root_element(**overrides)` and win on non-data keys.
|
|
432
|
-
def build_root_element_attributes(overrides)
|
|
433
|
-
plan = seal_draft
|
|
434
|
-
data_attrs = ::Vident2::Internals::AttributeWriter.call(plan)
|
|
435
|
-
|
|
436
|
-
extra = resolved_root_element_attributes
|
|
437
|
-
extra_html_options = extra[:html_options] || {}
|
|
438
|
-
extra_class = extra[:classes]
|
|
439
|
-
extra_id = extra[:id]
|
|
440
|
-
extra_data = extra_html_options[:data] || {}
|
|
441
|
-
|
|
442
|
-
# data precedence (low→high): Plan fragments → attrs html_options[:data]
|
|
443
|
-
# → instance html_options[:data] → overrides[:data].
|
|
444
|
-
merged_data = data_attrs.dup
|
|
445
|
-
merged_data.merge!(symbolize_keys(extra_data))
|
|
446
|
-
merged_data.merge!(symbolize_keys(@html_options[:data] || {}))
|
|
447
|
-
merged_data.merge!(symbolize_keys(overrides[:data] || {}))
|
|
448
|
-
|
|
449
|
-
# 6-tier class-list cascade — see ClassListBuilder.
|
|
450
|
-
class_list = ::Vident2::Internals::ClassListBuilder.call(
|
|
451
|
-
component_name: component_name,
|
|
452
|
-
root_element_classes: root_element_classes,
|
|
453
|
-
root_element_attributes_classes: extra_class,
|
|
454
|
-
root_element_html_class: overrides[:class],
|
|
455
|
-
html_options_class: (@html_options[:class] || extra_html_options[:class]),
|
|
456
|
-
classes_prop: @classes,
|
|
457
|
-
tailwind_merger: tailwind_merger
|
|
458
|
-
)
|
|
459
|
-
|
|
460
|
-
merged = {}
|
|
461
|
-
merged.merge!(extra_html_options.except(:data, :class))
|
|
462
|
-
merged.merge!(@html_options.except(:data, :class))
|
|
463
|
-
merged.merge!(overrides.except(:data, :class))
|
|
464
|
-
merged[:class] = class_list if class_list
|
|
465
|
-
merged[:data] = merged_data unless merged_data.empty?
|
|
466
|
-
merged[:id] ||= extra_id || id
|
|
467
|
-
|
|
468
|
-
merged
|
|
469
|
-
end
|
|
470
|
-
|
|
471
|
-
def symbolize_keys(hash)
|
|
472
|
-
return {} unless hash.is_a?(Hash)
|
|
473
|
-
hash.transform_keys { |k| k.is_a?(String) ? k.to_sym : k }
|
|
474
|
-
end
|
|
475
|
-
|
|
476
|
-
def root_element_tag_type
|
|
477
|
-
tag = resolved_root_element_attributes[:element_tag] || @element_tag
|
|
478
|
-
tag.presence&.to_sym || :div
|
|
479
|
-
end
|
|
480
|
-
|
|
481
|
-
# --- child_element helpers ------------------------------------------
|
|
482
|
-
|
|
483
|
-
def child_element_check_plural!(plural, singular, kind)
|
|
484
|
-
if plural && singular
|
|
485
|
-
raise ArgumentError,
|
|
486
|
-
"'stimulus_#{kind.plural_name}:' and 'stimulus_#{SINGULAR_NAMES.fetch(kind.name)}:' " \
|
|
487
|
-
"are mutually exclusive — pass one or the other."
|
|
488
|
-
end
|
|
489
|
-
return if plural.nil?
|
|
490
|
-
return if plural.is_a?(Enumerable) && !plural.is_a?(Hash)
|
|
491
|
-
return if plural.is_a?(Hash) && kind.keyed
|
|
492
|
-
raise ArgumentError,
|
|
493
|
-
"'stimulus_#{kind.plural_name}:' must be an enumerable. " \
|
|
494
|
-
"Did you mean 'stimulus_#{SINGULAR_NAMES.fetch(kind.name)}:'?"
|
|
495
|
-
end
|
|
496
|
-
|
|
497
|
-
# Exactly one of `plural` / `singular` is non-nil; guard above
|
|
498
|
-
# rejects both-set.
|
|
499
|
-
def child_element_build_collection(kind, plural, singular)
|
|
500
|
-
plural_method = :"stimulus_#{kind.plural_name}"
|
|
501
|
-
singular_method = :"stimulus_#{SINGULAR_NAMES.fetch(kind.name)}"
|
|
502
|
-
|
|
503
|
-
if plural
|
|
504
|
-
if kind.keyed && plural.is_a?(Hash)
|
|
505
|
-
send(plural_method, plural)
|
|
506
|
-
elsif plural.is_a?(Array)
|
|
507
|
-
send(plural_method, *plural)
|
|
508
|
-
else
|
|
509
|
-
send(plural_method, *Array(plural))
|
|
510
|
-
end
|
|
511
|
-
elsif singular
|
|
512
|
-
coll_items = [send(singular_method, *Array.wrap(singular))]
|
|
513
|
-
::Vident2::Stimulus::Collection.new(kind: kind, items: coll_items)
|
|
514
|
-
else
|
|
515
|
-
::Vident2::Stimulus::Collection.new(kind: kind, items: [])
|
|
516
|
-
end
|
|
517
|
-
end
|
|
518
|
-
|
|
519
|
-
public
|
|
520
|
-
|
|
521
|
-
def root_element(**overrides, &block)
|
|
522
|
-
raise NoMethodError, "subclass must implement root_element"
|
|
523
|
-
end
|
|
524
|
-
|
|
525
|
-
# Dispatches to the adapter-specific `root_element` on subclasses
|
|
526
|
-
# (Phlex / ViewComponent). Keep as `def` not `alias_method` so Ruby's
|
|
527
|
-
# dynamic dispatch finds the subclass override.
|
|
528
|
-
def root(...)
|
|
529
|
-
root_element(...)
|
|
530
|
-
end
|
|
531
|
-
|
|
532
|
-
# @api private — adapter override point. Phlex: Phlex tag DSL + invalid-
|
|
533
|
-
# tag guard. VC: content_tag / tag.
|
|
534
|
-
def generate_child_element(tag_name, stimulus_data_attributes, options, &block)
|
|
535
|
-
raise NoMethodError, "adapter must implement generate_child_element"
|
|
536
|
-
end
|
|
537
|
-
end
|
|
538
|
-
end
|
data/lib/vident2/engine.rb
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Vident2
|
|
4
|
-
# Rails engine hook for Vident2. Mirrors `Vident::Engine`'s autoload
|
|
5
|
-
# setup (both live under `lib/`) and registers the V2-specific
|
|
6
|
-
# acronym inflections Zeitwerk needs to load `Vident2::Internals::DSL`
|
|
7
|
-
# and `Vident2::Phlex::HTML` from their respective files.
|
|
8
|
-
class Engine < ::Rails::Engine
|
|
9
|
-
config.before_initialize do
|
|
10
|
-
Rails.autoloaders.each do |autoloader|
|
|
11
|
-
autoloader.inflector.inflect(
|
|
12
|
-
"dsl" => "DSL",
|
|
13
|
-
"html" => "HTML"
|
|
14
|
-
)
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
end
|
data/lib/vident2/error.rb
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Vident2
|
|
4
|
-
# Root of the Vident2 error hierarchy. Every gem-raised exception
|
|
5
|
-
# inherits from this; consumers can rescue by category.
|
|
6
|
-
class Error < StandardError; end
|
|
7
|
-
|
|
8
|
-
# Raised at class-definition time when a `stimulus do` block is
|
|
9
|
-
# structurally incompatible with the class (e.g. a `no_stimulus_controller`
|
|
10
|
-
# class emitting DSL entries). Carries a caller location pointing at
|
|
11
|
-
# the offending `stimulus do` call.
|
|
12
|
-
class DeclarationError < Error; end
|
|
13
|
-
|
|
14
|
-
# Raised when a value-class parser cannot make sense of its arguments.
|
|
15
|
-
# Subclass of DeclarationError because most parse failures originate
|
|
16
|
-
# from DSL input recorded at class load.
|
|
17
|
-
class ParseError < DeclarationError; end
|
|
18
|
-
|
|
19
|
-
# Raised when a proc evaluated during render resolution fails or
|
|
20
|
-
# returns an unusable shape.
|
|
21
|
-
class RenderError < Error; end
|
|
22
|
-
|
|
23
|
-
# Raised when a mutator (e.g. `add_stimulus_actions`) is invoked on a
|
|
24
|
-
# sealed Draft.
|
|
25
|
-
class StateError < Error; end
|
|
26
|
-
|
|
27
|
-
# Raised for misconfiguration at the gem or host level (e.g. unknown
|
|
28
|
-
# StableId strategy).
|
|
29
|
-
class ConfigurationError < Error; end
|
|
30
|
-
end
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "registry"
|
|
4
|
-
|
|
5
|
-
module Vident2
|
|
6
|
-
module Internals
|
|
7
|
-
# @api private
|
|
8
|
-
# Pure: `Plan -> Hash{Symbol => String}` of `data-*` fragments.
|
|
9
|
-
# Delegates per-kind combining (space-join, grouped-by-controller,
|
|
10
|
-
# one-per-key) to each value class's `.to_data_hash`.
|
|
11
|
-
module AttributeWriter
|
|
12
|
-
module_function
|
|
13
|
-
|
|
14
|
-
def call(plan)
|
|
15
|
-
Registry::KINDS.each_with_object({}) do |kind, acc|
|
|
16
|
-
fragment = kind.value_class.to_data_hash(plan.public_send(kind.name))
|
|
17
|
-
acc.merge!(fragment)
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
end
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Vident2
|
|
4
|
-
module Internals
|
|
5
|
-
# @api private
|
|
6
|
-
# One unresolved DSL entry. `args` is the raw argument tuple passed
|
|
7
|
-
# to the DSL primitive; the Resolver parses it into a Stimulus value
|
|
8
|
-
# object at instance init. `when_proc` (optional) is a `-> { ... }`
|
|
9
|
-
# filter evaluated in the component binding; `meta` is a free-form
|
|
10
|
-
# Hash for options like `from_prop: true` the parser needs to see.
|
|
11
|
-
Declaration = Data.define(:args, :when_proc, :meta) do
|
|
12
|
-
def self.of(*args, when_proc: nil, **meta)
|
|
13
|
-
new(args: args.freeze, when_proc: when_proc, meta: meta.freeze)
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "registry"
|
|
4
|
-
|
|
5
|
-
module Vident2
|
|
6
|
-
module Internals
|
|
7
|
-
# @api private
|
|
8
|
-
# Frozen snapshot produced by `Draft#seal!`. One field per Registry
|
|
9
|
-
# kind, each an Array<Stimulus::*>.
|
|
10
|
-
Plan = Data.define(*Registry.names)
|
|
11
|
-
end
|
|
12
|
-
end
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "../stimulus/controller"
|
|
4
|
-
require_relative "../stimulus/action"
|
|
5
|
-
require_relative "../stimulus/target"
|
|
6
|
-
require_relative "../stimulus/outlet"
|
|
7
|
-
require_relative "../stimulus/value"
|
|
8
|
-
require_relative "../stimulus/param"
|
|
9
|
-
require_relative "../stimulus/class_map"
|
|
10
|
-
|
|
11
|
-
module Vident2
|
|
12
|
-
# @api private — consumed by the DSL, Resolver, Draft, Plan,
|
|
13
|
-
# AttributeWriter, and Capabilities::StimulusMutation. Not a public
|
|
14
|
-
# extension surface; extensions monkeypatch at their own risk.
|
|
15
|
-
module Internals
|
|
16
|
-
module Registry
|
|
17
|
-
# Per-kind metadata. `name` is the canonical internal key;
|
|
18
|
-
# `plural_name` / `singular_name` drive DSL method names (e.g.
|
|
19
|
-
# `stimulus_classes` / `stimulus_class` for `class_maps`); `keyed`
|
|
20
|
-
# distinguishes hash-shaped kinds (values, params, class_maps,
|
|
21
|
-
# outlets) from positional ones (controllers, actions, targets).
|
|
22
|
-
Kind = Data.define(:name, :plural_name, :singular_name, :value_class, :keyed)
|
|
23
|
-
|
|
24
|
-
KINDS = [
|
|
25
|
-
Kind.new(:controllers, :controllers, :controller, Vident2::Stimulus::Controller, false),
|
|
26
|
-
Kind.new(:actions, :actions, :action, Vident2::Stimulus::Action, false),
|
|
27
|
-
Kind.new(:targets, :targets, :target, Vident2::Stimulus::Target, false),
|
|
28
|
-
Kind.new(:outlets, :outlets, :outlet, Vident2::Stimulus::Outlet, true),
|
|
29
|
-
Kind.new(:values, :values, :value, Vident2::Stimulus::Value, true),
|
|
30
|
-
Kind.new(:params, :params, :param, Vident2::Stimulus::Param, true),
|
|
31
|
-
Kind.new(:class_maps, :classes, :class, Vident2::Stimulus::ClassMap, true)
|
|
32
|
-
].freeze
|
|
33
|
-
|
|
34
|
-
BY_NAME = KINDS.to_h { |k| [k.name, k] }.freeze
|
|
35
|
-
|
|
36
|
-
def self.fetch(name) = BY_NAME.fetch(name)
|
|
37
|
-
def self.each(&block) = KINDS.each(&block)
|
|
38
|
-
def self.names = BY_NAME.keys
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|