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.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +54 -0
  3. data/README.md +49 -18
  4. data/lib/vident/caching.rb +4 -110
  5. data/lib/vident/capabilities/caching.rb +98 -0
  6. data/lib/vident/capabilities/child_element_rendering.rb +92 -0
  7. data/lib/vident/capabilities/class_list_building.rb +23 -0
  8. data/lib/vident/capabilities/declarable.rb +39 -0
  9. data/lib/vident/capabilities/identifiable.rb +54 -0
  10. data/lib/vident/capabilities/inspectable.rb +17 -0
  11. data/lib/vident/capabilities/root_element_rendering.rb +31 -0
  12. data/lib/vident/capabilities/stimulus_data_emitting.rb +98 -0
  13. data/lib/vident/capabilities/stimulus_declaring.rb +79 -0
  14. data/lib/vident/capabilities/stimulus_draft.rb +51 -0
  15. data/lib/vident/capabilities/stimulus_mutation.rb +60 -0
  16. data/lib/vident/capabilities/stimulus_parsing.rb +144 -0
  17. data/lib/vident/capabilities/tailwind.rb +18 -0
  18. data/lib/vident/component.rb +14 -76
  19. data/lib/vident/engine.rb +6 -5
  20. data/lib/vident/error.rb +16 -0
  21. data/lib/vident/internals/action_builder.rb +97 -0
  22. data/lib/vident/internals/attribute_writer.rb +17 -0
  23. data/lib/vident/internals/class_list_builder.rb +62 -0
  24. data/lib/vident/internals/declaration.rb +13 -0
  25. data/lib/vident/internals/declarations.rb +64 -0
  26. data/lib/vident/internals/draft.rb +47 -0
  27. data/lib/vident/internals/dsl.rb +172 -0
  28. data/lib/vident/internals/plan.rb +9 -0
  29. data/lib/vident/internals/registry.rb +37 -0
  30. data/lib/vident/internals/resolver.rb +316 -0
  31. data/lib/vident/internals/target_builder.rb +23 -0
  32. data/lib/vident/stable_id.rb +3 -3
  33. data/lib/vident/stimulus/action.rb +127 -0
  34. data/lib/vident/stimulus/base.rb +26 -0
  35. data/lib/vident/stimulus/class_map.rb +57 -0
  36. data/lib/vident/stimulus/collection.rb +40 -0
  37. data/lib/vident/stimulus/combinable.rb +30 -0
  38. data/lib/vident/stimulus/controller.rb +45 -0
  39. data/lib/vident/stimulus/naming.rb +9 -9
  40. data/lib/vident/stimulus/null.rb +7 -0
  41. data/lib/vident/stimulus/outlet.rb +93 -0
  42. data/lib/vident/stimulus/param.rb +56 -0
  43. data/lib/vident/stimulus/target.rb +48 -0
  44. data/lib/vident/stimulus/value.rb +57 -0
  45. data/lib/vident/stimulus_null.rb +4 -8
  46. data/lib/vident/tailwind.rb +4 -17
  47. data/lib/vident/types.rb +28 -0
  48. data/lib/vident/version.rb +1 -6
  49. data/lib/vident.rb +44 -36
  50. data/skills/vident/SKILL.md +133 -21
  51. data/skills/vident/api-reference.md +662 -0
  52. data/skills/vident/examples.md +505 -0
  53. metadata +40 -28
  54. data/lib/vident/child_element_helper.rb +0 -64
  55. data/lib/vident/class_list_builder.rb +0 -112
  56. data/lib/vident/component_attribute_resolver.rb +0 -87
  57. data/lib/vident/component_class_lists.rb +0 -34
  58. data/lib/vident/stimulus/primitive.rb +0 -38
  59. data/lib/vident/stimulus.rb +0 -31
  60. data/lib/vident/stimulus_action.rb +0 -133
  61. data/lib/vident/stimulus_action_collection.rb +0 -11
  62. data/lib/vident/stimulus_attribute_base.rb +0 -67
  63. data/lib/vident/stimulus_attributes.rb +0 -129
  64. data/lib/vident/stimulus_builder.rb +0 -119
  65. data/lib/vident/stimulus_class.rb +0 -59
  66. data/lib/vident/stimulus_class_collection.rb +0 -11
  67. data/lib/vident/stimulus_collection_base.rb +0 -51
  68. data/lib/vident/stimulus_component.rb +0 -75
  69. data/lib/vident/stimulus_controller.rb +0 -41
  70. data/lib/vident/stimulus_controller_collection.rb +0 -14
  71. data/lib/vident/stimulus_data_attribute_builder.rb +0 -32
  72. data/lib/vident/stimulus_helper.rb +0 -66
  73. data/lib/vident/stimulus_outlet.rb +0 -90
  74. data/lib/vident/stimulus_outlet_collection.rb +0 -11
  75. data/lib/vident/stimulus_param.rb +0 -42
  76. data/lib/vident/stimulus_param_collection.rb +0 -11
  77. data/lib/vident/stimulus_target.rb +0 -47
  78. data/lib/vident/stimulus_target_collection.rb +0 -18
  79. data/lib/vident/stimulus_value.rb +0 -39
  80. 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,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Vident
4
- class StimulusClassCollection < StimulusCollectionBase
5
- def to_h
6
- return {} if items.empty?
7
-
8
- items.each_with_object({}) { |css_class, merged| merged.merge!(css_class.to_h) }
9
- end
10
- end
11
- 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,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Vident
4
- class StimulusOutletCollection < StimulusCollectionBase
5
- def to_h
6
- return {} if items.empty?
7
-
8
- items.each_with_object({}) { |outlet, merged| merged.merge!(outlet.to_h) }
9
- end
10
- end
11
- 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,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Vident
4
- class StimulusParamCollection < StimulusCollectionBase
5
- def to_h
6
- return {} if items.empty?
7
-
8
- items.each_with_object({}) { |param, merged| merged.merge!(param.to_h) }
9
- end
10
- end
11
- 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