vident 1.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +54 -0
- data/README.md +49 -18
- 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/vident/internals/action_builder.rb +97 -0
- data/lib/vident/internals/attribute_writer.rb +17 -0
- data/lib/vident/internals/class_list_builder.rb +62 -0
- data/lib/vident/internals/declaration.rb +13 -0
- data/lib/vident/internals/declarations.rb +64 -0
- data/lib/vident/internals/draft.rb +47 -0
- data/lib/vident/internals/dsl.rb +172 -0
- data/lib/vident/internals/plan.rb +9 -0
- data/lib/vident/internals/registry.rb +37 -0
- data/lib/vident/internals/resolver.rb +316 -0
- data/lib/vident/internals/target_builder.rb +23 -0
- data/lib/vident/stable_id.rb +3 -3
- data/lib/vident/stimulus/action.rb +127 -0
- data/lib/vident/stimulus/base.rb +26 -0
- data/lib/vident/stimulus/class_map.rb +57 -0
- data/lib/vident/stimulus/collection.rb +40 -0
- 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/vident/stimulus/outlet.rb +93 -0
- data/lib/vident/stimulus/param.rb +56 -0
- data/lib/vident/stimulus/target.rb +48 -0
- 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 +44 -36
- data/skills/vident/SKILL.md +133 -21
- data/skills/vident/api-reference.md +662 -0
- data/skills/vident/examples.md +505 -0
- metadata +40 -28
- 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 -87
- data/lib/vident/component_class_lists.rb +0 -34
- 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 -119
- 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
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Vident
|
|
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
|
-
|
|
11
|
-
def initialize
|
|
12
|
-
@entries = DSL_PRIMITIVES.to_h { |primitive| [primitive.name, primitive.keyed? ? {} : []] }
|
|
13
|
-
@values_from_props = []
|
|
14
|
-
end
|
|
15
|
-
|
|
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)
|
|
28
|
-
self
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def targets(*names)
|
|
32
|
-
@entries[:targets].concat(names)
|
|
33
|
-
self
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def values(**hash)
|
|
37
|
-
@entries[:values].merge!(hash) unless hash.empty?
|
|
38
|
-
self
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def params(**hash)
|
|
42
|
-
@entries[:params].merge!(hash) unless hash.empty?
|
|
43
|
-
self
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def classes(**hash)
|
|
47
|
-
@entries[:classes].merge!(hash) unless hash.empty?
|
|
48
|
-
self
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def values_from_props(*names)
|
|
52
|
-
@values_from_props.concat(names)
|
|
53
|
-
self
|
|
54
|
-
end
|
|
55
|
-
|
|
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?
|
|
62
|
-
self
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def to_attributes(component_instance)
|
|
66
|
-
attrs = {}
|
|
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
|
|
72
|
-
attrs[:stimulus_values_from_props] = @values_from_props.dup unless @values_from_props.empty?
|
|
73
|
-
attrs
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def to_hash(component_instance) = to_attributes(component_instance)
|
|
77
|
-
alias_method :to_h, :to_hash
|
|
78
|
-
|
|
79
|
-
protected
|
|
80
|
-
|
|
81
|
-
def entries_for(name) = @entries[name]
|
|
82
|
-
|
|
83
|
-
def values_from_props_list = @values_from_props
|
|
84
|
-
|
|
85
|
-
private
|
|
86
|
-
|
|
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)
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
|
|
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
|
|
105
|
-
end
|
|
106
|
-
|
|
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?
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
def callable?(value) = value.respond_to?(:call)
|
|
118
|
-
end
|
|
119
|
-
end
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Vident
|
|
4
|
-
class StimulusClass < StimulusAttributeBase
|
|
5
|
-
attr_reader :controller, :class_name, :css_classes
|
|
6
|
-
|
|
7
|
-
def to_s = @css_classes.join(" ")
|
|
8
|
-
|
|
9
|
-
def data_attribute_name = "#{@controller}-#{@class_name}-class"
|
|
10
|
-
|
|
11
|
-
def data_attribute_value = to_s
|
|
12
|
-
|
|
13
|
-
private
|
|
14
|
-
|
|
15
|
-
def parse_arguments(*args)
|
|
16
|
-
case args.size
|
|
17
|
-
when 2
|
|
18
|
-
parse_two_arguments(args[0], args[1])
|
|
19
|
-
when 3
|
|
20
|
-
parse_three_arguments(args[0], args[1], args[2])
|
|
21
|
-
else
|
|
22
|
-
raise ArgumentError, "Invalid number of arguments: #{args.size}"
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def parse_two_arguments(class_name, css_classes)
|
|
27
|
-
if class_name.is_a?(Symbol)
|
|
28
|
-
# class name on implied controller + css classes
|
|
29
|
-
@controller = implied_controller_name
|
|
30
|
-
@class_name = class_name.to_s.dasherize
|
|
31
|
-
@css_classes = normalize_css_classes(css_classes)
|
|
32
|
-
else
|
|
33
|
-
raise ArgumentError, "Invalid argument types: #{class_name.class}, #{css_classes.class}"
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def parse_three_arguments(controller, class_name, css_classes)
|
|
38
|
-
if controller.is_a?(String) && class_name.is_a?(Symbol)
|
|
39
|
-
# controller + class name + css classes
|
|
40
|
-
@controller = stimulize_path(controller)
|
|
41
|
-
@class_name = class_name.to_s.dasherize
|
|
42
|
-
@css_classes = normalize_css_classes(css_classes)
|
|
43
|
-
else
|
|
44
|
-
raise ArgumentError, "Invalid argument types: #{controller.class}, #{class_name.class}, #{css_classes.class}"
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def normalize_css_classes(css_classes)
|
|
49
|
-
case css_classes
|
|
50
|
-
when String
|
|
51
|
-
css_classes.split(/\s+/).reject(&:empty?)
|
|
52
|
-
when Array
|
|
53
|
-
css_classes.map(&:to_s).reject(&:empty?)
|
|
54
|
-
else
|
|
55
|
-
raise ArgumentError, "CSS classes must be a String or Array, got #{css_classes.class}"
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Vident
|
|
4
|
-
class StimulusCollectionBase
|
|
5
|
-
def initialize(items = [])
|
|
6
|
-
@items = Array(items).flatten.compact
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
def <<(item)
|
|
10
|
-
@items << item
|
|
11
|
-
self
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def to_h
|
|
15
|
-
raise NoMethodError, "Subclasses must implement to_h"
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def to_a = @items.dup
|
|
19
|
-
|
|
20
|
-
def to_hash = to_h
|
|
21
|
-
|
|
22
|
-
def empty? = @items.empty?
|
|
23
|
-
|
|
24
|
-
def any? = !empty?
|
|
25
|
-
|
|
26
|
-
def merge(*other_collections)
|
|
27
|
-
merged = self.class.new
|
|
28
|
-
merged.instance_variable_set(:@items, @items.dup)
|
|
29
|
-
|
|
30
|
-
other_collections.each do |collection|
|
|
31
|
-
next unless collection.is_a?(self.class)
|
|
32
|
-
merged.instance_variable_get(:@items).concat(collection.instance_variable_get(:@items))
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
merged
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def self.merge(*collections)
|
|
39
|
-
return new if collections.empty?
|
|
40
|
-
|
|
41
|
-
first_collection = collections.first
|
|
42
|
-
return first_collection if collections.size == 1
|
|
43
|
-
|
|
44
|
-
first_collection.merge(*collections[1..-1])
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
protected
|
|
48
|
-
|
|
49
|
-
attr_reader :items
|
|
50
|
-
end
|
|
51
|
-
end
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Vident
|
|
4
|
-
module StimulusComponent
|
|
5
|
-
extend ActiveSupport::Concern
|
|
6
|
-
|
|
7
|
-
include StimulusAttributes
|
|
8
|
-
|
|
9
|
-
# Thin back-compat alias; see `Vident::StimulusAttributeBase.stimulize_path`
|
|
10
|
-
# for the canonical implementation.
|
|
11
|
-
def stimulus_identifier_from_path(path)
|
|
12
|
-
StimulusAttributeBase.stimulize_path(path)
|
|
13
|
-
end
|
|
14
|
-
module_function :stimulus_identifier_from_path
|
|
15
|
-
|
|
16
|
-
# Base class for all Vident components, which provides common functionality and properties.
|
|
17
|
-
|
|
18
|
-
class_methods do
|
|
19
|
-
def no_stimulus_controller
|
|
20
|
-
@no_stimulus_controller = true
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def stimulus_controller? = !@no_stimulus_controller
|
|
24
|
-
|
|
25
|
-
# The "path" of the Stimulus controller, which is used to generate the controller name.
|
|
26
|
-
def stimulus_identifier_path = name&.underscore || "anonymous_component"
|
|
27
|
-
|
|
28
|
-
# Stimulus controller identifier
|
|
29
|
-
def stimulus_identifier = StimulusComponent.stimulus_identifier_from_path(stimulus_identifier_path)
|
|
30
|
-
|
|
31
|
-
# The "name" of the component from its class name and namespace. This is used to generate an HTML class name
|
|
32
|
-
# that can helps identify the component type in the DOM or for styling purposes.
|
|
33
|
-
def component_name
|
|
34
|
-
@component_name ||= stimulus_identifier
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Components have the following properties
|
|
39
|
-
included do
|
|
40
|
-
extend Literal::Properties
|
|
41
|
-
|
|
42
|
-
# StimulusJS support
|
|
43
|
-
# # TODO: revisit inputs and how many ways of specifying the same thing...
|
|
44
|
-
prop :stimulus_controllers, _Array(_Union(String, Symbol, StimulusController, StimulusControllerCollection)), default: -> do
|
|
45
|
-
if self.class.stimulus_controller?
|
|
46
|
-
[default_controller_path]
|
|
47
|
-
else
|
|
48
|
-
[]
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
prop :stimulus_actions, _Array(_Union(String, Symbol, Array, Hash, StimulusAction, StimulusAction::Descriptor, StimulusActionCollection)), default: -> { [] }
|
|
52
|
-
prop :stimulus_targets, _Array(_Union(String, Symbol, Array, Hash, StimulusTarget, StimulusTargetCollection)), default: -> { [] }
|
|
53
|
-
prop :stimulus_outlets, _Array(_Union(String, Symbol, StimulusOutlet, StimulusOutletCollection)), default: -> { [] }
|
|
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), 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: -> { {} }
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
# If connecting an outlet to this specific component instance, use this ID
|
|
61
|
-
def outlet_id
|
|
62
|
-
@outlet_id ||= [stimulus_identifier, "##{id}"]
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# The Stimulus controller identifier for this component
|
|
66
|
-
def stimulus_identifier = self.class.stimulus_identifier
|
|
67
|
-
|
|
68
|
-
# An name that can helps identify the component type in the DOM or for styling purposes (its also used as a class name on the root element)
|
|
69
|
-
def component_name = self.class.component_name
|
|
70
|
-
|
|
71
|
-
# The `component` class name is used to create the controller name.
|
|
72
|
-
# The path of the Stimulus controller when none is explicitly set
|
|
73
|
-
def default_controller_path = self.class.stimulus_identifier_path
|
|
74
|
-
end
|
|
75
|
-
end
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Vident
|
|
4
|
-
class StimulusController < StimulusAttributeBase
|
|
5
|
-
attr_reader :path, :name
|
|
6
|
-
|
|
7
|
-
def to_s = name
|
|
8
|
-
|
|
9
|
-
def data_attribute_name = "controller"
|
|
10
|
-
|
|
11
|
-
def data_attribute_value = name
|
|
12
|
-
|
|
13
|
-
private
|
|
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.
|
|
18
|
-
def implied_controller_path
|
|
19
|
-
raise ArgumentError, "implied_controller is required to get implied controller path" unless @implied_controller
|
|
20
|
-
@implied_controller
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def implied_controller_name
|
|
24
|
-
raise ArgumentError, "implied_controller is required to get implied controller name" unless @implied_controller
|
|
25
|
-
stimulize_path(@implied_controller)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def parse_arguments(*args)
|
|
29
|
-
case args.size
|
|
30
|
-
when 0
|
|
31
|
-
@path = implied_controller_path
|
|
32
|
-
@name = implied_controller_name
|
|
33
|
-
when 1
|
|
34
|
-
@path = args[0].to_s
|
|
35
|
-
@name = stimulize_path(@path)
|
|
36
|
-
else
|
|
37
|
-
raise ArgumentError, "Invalid number of arguments: #{args.size}. Expected 0 or 1 argument."
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Vident
|
|
4
|
-
class StimulusControllerCollection < StimulusCollectionBase
|
|
5
|
-
def to_h
|
|
6
|
-
return {} if items.empty?
|
|
7
|
-
|
|
8
|
-
controller_values = items.map(&:to_s).reject(&:empty?)
|
|
9
|
-
return {} if controller_values.empty?
|
|
10
|
-
|
|
11
|
-
{controller: controller_values.join(" ")}
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Vident
|
|
4
|
-
class StimulusDataAttributeBuilder
|
|
5
|
-
def initialize(**collections_by_name)
|
|
6
|
-
unknown = collections_by_name.keys - Stimulus.names
|
|
7
|
-
raise ArgumentError, "Unknown stimulus primitive(s) #{unknown.inspect}" if unknown.any?
|
|
8
|
-
|
|
9
|
-
@collections_by_name = collections_by_name.transform_values { |v| Array(v) }
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def build
|
|
13
|
-
Stimulus::PRIMITIVES.each_with_object({}) do |primitive, attrs|
|
|
14
|
-
attrs.merge!(merge_collection(primitive.collection_class, @collections_by_name[primitive.name] || []))
|
|
15
|
-
end.transform_keys(&:to_s).compact
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
private
|
|
19
|
-
|
|
20
|
-
# Items are either pre-built collections (DSL / resolver path) or raw value
|
|
21
|
-
# objects (child_element path). Merge-or-wrap accordingly.
|
|
22
|
-
def merge_collection(collection_class, items)
|
|
23
|
-
return {} if items.empty?
|
|
24
|
-
|
|
25
|
-
if items.first.is_a?(collection_class)
|
|
26
|
-
collection_class.merge(*items).to_h
|
|
27
|
-
else
|
|
28
|
-
collection_class.new(items).to_h
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Vident
|
|
4
|
-
module StimulusHelper
|
|
5
|
-
extend ActiveSupport::Concern
|
|
6
|
-
|
|
7
|
-
class_methods do
|
|
8
|
-
def stimulus(&block)
|
|
9
|
-
# Initialize stimulus builder if not already present
|
|
10
|
-
if @stimulus_builder.nil?
|
|
11
|
-
@stimulus_builder = StimulusBuilder.new
|
|
12
|
-
@inheritance_merged = false
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
# Ensure inheritance is applied
|
|
16
|
-
ensure_inheritance_merged
|
|
17
|
-
|
|
18
|
-
# Execute the new block to add/merge new attributes
|
|
19
|
-
@stimulus_builder.instance_eval(&block)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def stimulus_dsl_attributes(component_instance)
|
|
23
|
-
# If no stimulus blocks have been defined on this class, check parent
|
|
24
|
-
if @stimulus_builder.nil? && superclass.respond_to?(:stimulus_dsl_attributes)
|
|
25
|
-
return superclass.stimulus_dsl_attributes(component_instance)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Ensure inheritance is applied at access time
|
|
29
|
-
ensure_inheritance_merged
|
|
30
|
-
|
|
31
|
-
@stimulus_builder&.to_attributes(component_instance) || {}
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
private
|
|
35
|
-
|
|
36
|
-
def ensure_inheritance_merged
|
|
37
|
-
return if @inheritance_merged || @stimulus_builder.nil?
|
|
38
|
-
|
|
39
|
-
if superclass.respond_to?(:stimulus_dsl_builder, true)
|
|
40
|
-
parent_builder = superclass.send(:stimulus_dsl_builder)
|
|
41
|
-
if parent_builder
|
|
42
|
-
@stimulus_builder.merge_with(parent_builder)
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
@inheritance_merged = true
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
protected
|
|
49
|
-
|
|
50
|
-
def stimulus_dsl_builder = @stimulus_builder
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
# Instance method to get DSL attributes for this component instance
|
|
54
|
-
def stimulus_dsl_attributes = self.class.stimulus_dsl_attributes(self)
|
|
55
|
-
|
|
56
|
-
# Instance method to resolve prop-mapped values at runtime
|
|
57
|
-
def resolve_values_from_props(prop_names)
|
|
58
|
-
return {} if prop_names.empty?
|
|
59
|
-
|
|
60
|
-
prop_names.each_with_object({}) do |name, resolved|
|
|
61
|
-
# Map from instance variable if it exists
|
|
62
|
-
resolved[name] = instance_variable_get("@#{name}") if instance_variable_defined?("@#{name}")
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Vident
|
|
4
|
-
class StimulusOutlet < StimulusAttributeBase
|
|
5
|
-
attr_reader :controller, :outlet_name, :selector
|
|
6
|
-
|
|
7
|
-
def initialize(*args, implied_controller:, component_id: nil)
|
|
8
|
-
@component_id = component_id
|
|
9
|
-
super(*args, implied_controller: implied_controller)
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def to_s = @selector
|
|
13
|
-
|
|
14
|
-
def data_attribute_name = "#{@controller}-#{@outlet_name}-outlet"
|
|
15
|
-
|
|
16
|
-
def data_attribute_value = @selector
|
|
17
|
-
|
|
18
|
-
private
|
|
19
|
-
|
|
20
|
-
def parse_arguments(*args)
|
|
21
|
-
case args.size
|
|
22
|
-
when 1
|
|
23
|
-
parse_single_argument(args[0])
|
|
24
|
-
when 2
|
|
25
|
-
parse_two_arguments(args[0], args[1])
|
|
26
|
-
when 3
|
|
27
|
-
parse_three_arguments(args[0], args[1], args[2])
|
|
28
|
-
else
|
|
29
|
-
raise ArgumentError, "Invalid number of arguments: #{args.size}"
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def parse_single_argument(arg)
|
|
34
|
-
@controller = implied_controller_name
|
|
35
|
-
if arg.is_a?(Symbol)
|
|
36
|
-
# Single symbol: outlet name on implied controller with auto-generated selector
|
|
37
|
-
outlet_identifier = arg.to_s.dasherize
|
|
38
|
-
@outlet_name = outlet_identifier
|
|
39
|
-
@selector = build_outlet_selector(outlet_identifier)
|
|
40
|
-
elsif arg.is_a?(String)
|
|
41
|
-
# Single string: outlet identifier with auto-generated selector
|
|
42
|
-
@outlet_name = arg.dasherize
|
|
43
|
-
@selector = build_outlet_selector(arg)
|
|
44
|
-
elsif arg.is_a?(Array) && arg.size == 2
|
|
45
|
-
# Array format: [outlet_identifier, css_selector]
|
|
46
|
-
@outlet_name = arg[0].to_s.dasherize
|
|
47
|
-
@selector = arg[1]
|
|
48
|
-
elsif arg.respond_to?(:stimulus_identifier)
|
|
49
|
-
# Component with stimulus_identifier
|
|
50
|
-
identifier = arg.stimulus_identifier
|
|
51
|
-
@outlet_name = identifier
|
|
52
|
-
@selector = build_outlet_selector(identifier)
|
|
53
|
-
elsif arg.respond_to?(:implied_controller_name)
|
|
54
|
-
# RootComponent with implied_controller_name
|
|
55
|
-
identifier = arg.implied_controller_name
|
|
56
|
-
@outlet_name = identifier
|
|
57
|
-
@selector = build_outlet_selector(identifier)
|
|
58
|
-
else
|
|
59
|
-
raise ArgumentError, "Invalid argument type: #{arg.class}"
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def parse_two_arguments(arg1, arg2)
|
|
64
|
-
if (arg1.is_a?(Symbol) || arg1.is_a?(String)) && arg2.is_a?(String)
|
|
65
|
-
@controller = implied_controller_name
|
|
66
|
-
@outlet_name = arg1.to_s.dasherize
|
|
67
|
-
@selector = arg2
|
|
68
|
-
else
|
|
69
|
-
raise ArgumentError, "Invalid argument types: #{arg1.class}, #{arg2.class}"
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def parse_three_arguments(controller, outlet_name, selector)
|
|
74
|
-
if controller.is_a?(String) && outlet_name.is_a?(Symbol) && selector.is_a?(String)
|
|
75
|
-
# controller path + outlet name + selector
|
|
76
|
-
@controller = stimulize_path(controller)
|
|
77
|
-
@outlet_name = outlet_name.to_s.dasherize
|
|
78
|
-
@selector = selector
|
|
79
|
-
else
|
|
80
|
-
raise ArgumentError, "Invalid argument types: #{controller.class}, #{outlet_name.class}, #{selector.class}"
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# Build outlet selector following the same pattern as RootComponent
|
|
85
|
-
def build_outlet_selector(outlet_selector)
|
|
86
|
-
prefix = @component_id ? "##{@component_id} " : ""
|
|
87
|
-
"#{prefix}[data-controller~=#{outlet_selector}]"
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
end
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Vident
|
|
4
|
-
# `data-<controller>-<name>-param="..."` — readable on the JS side as
|
|
5
|
-
# `event.params.<camelName>`. Element-scoped: every action on the element
|
|
6
|
-
# sees the same params.
|
|
7
|
-
class StimulusParam < StimulusAttributeBase
|
|
8
|
-
attr_reader :controller, :param_name, :value
|
|
9
|
-
|
|
10
|
-
def to_s = @value.to_s
|
|
11
|
-
|
|
12
|
-
def data_attribute_name = "#{@controller}-#{@param_name}-param"
|
|
13
|
-
|
|
14
|
-
def data_attribute_value = @value
|
|
15
|
-
|
|
16
|
-
private
|
|
17
|
-
|
|
18
|
-
def parse_arguments(*args)
|
|
19
|
-
case args.size
|
|
20
|
-
when 2 then parse_two_arguments(*args)
|
|
21
|
-
when 3 then parse_three_arguments(*args)
|
|
22
|
-
else raise ArgumentError, "Invalid number of arguments: #{args.size} (#{args.inspect}). Did you pass an array of hashes?"
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def parse_two_arguments(param_name, value)
|
|
27
|
-
raise ArgumentError, "Invalid argument types: #{param_name.class}, #{value.class}" unless param_name.is_a?(Symbol)
|
|
28
|
-
@controller = implied_controller_name
|
|
29
|
-
@param_name = param_name.to_s.dasherize
|
|
30
|
-
@value = serialize_value(value)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def parse_three_arguments(controller, param_name, value)
|
|
34
|
-
unless controller.is_a?(String) && param_name.is_a?(Symbol)
|
|
35
|
-
raise ArgumentError, "Invalid argument types: #{controller.class}, #{param_name.class}, #{value.class}"
|
|
36
|
-
end
|
|
37
|
-
@controller = stimulize_path(controller)
|
|
38
|
-
@param_name = param_name.to_s.dasherize
|
|
39
|
-
@value = serialize_value(value)
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Vident
|
|
4
|
-
class StimulusTarget < StimulusAttributeBase
|
|
5
|
-
attr_reader :controller, :name
|
|
6
|
-
|
|
7
|
-
def to_s = @name
|
|
8
|
-
|
|
9
|
-
# Returns the data attribute name for this target
|
|
10
|
-
def data_attribute_name = "#{@controller}-target"
|
|
11
|
-
|
|
12
|
-
# Returns the target name value for the data attribute
|
|
13
|
-
def data_attribute_value = @name
|
|
14
|
-
|
|
15
|
-
private
|
|
16
|
-
|
|
17
|
-
def parse_arguments(*args)
|
|
18
|
-
case args.size
|
|
19
|
-
when 1
|
|
20
|
-
parse_single_argument(args[0])
|
|
21
|
-
when 2
|
|
22
|
-
parse_two_arguments(args[0], args[1])
|
|
23
|
-
else
|
|
24
|
-
raise ArgumentError, "Invalid number of arguments: #{args.size}"
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def parse_single_argument(arg)
|
|
29
|
-
@controller = implied_controller_name
|
|
30
|
-
@name = case arg
|
|
31
|
-
when Symbol then js_name(arg) # name of target on implied controller
|
|
32
|
-
when String then arg # target name on implied controller
|
|
33
|
-
else raise ArgumentError, "Invalid argument type: #{arg.class}"
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def parse_two_arguments(part1, part2)
|
|
38
|
-
if part1.is_a?(String) && part2.is_a?(Symbol)
|
|
39
|
-
# 1 string arg, 1 symbol = controller + target
|
|
40
|
-
@controller = stimulize_path(part1)
|
|
41
|
-
@name = js_name(part2)
|
|
42
|
-
else
|
|
43
|
-
raise ArgumentError, "Invalid argument types: #{part1.class}, #{part2.class}"
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|