vident 1.0.2 → 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 (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +43 -0
  3. data/README.md +45 -17
  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/{vident2 → vident}/internals/action_builder.rb +18 -22
  22. data/lib/vident/internals/attribute_writer.rb +17 -0
  23. data/lib/{vident2 → vident}/internals/class_list_builder.rb +5 -22
  24. data/lib/vident/internals/declaration.rb +13 -0
  25. data/lib/{vident2 → vident}/internals/declarations.rb +6 -18
  26. data/lib/{vident2 → vident}/internals/draft.rb +3 -16
  27. data/lib/{vident2 → vident}/internals/dsl.rb +6 -32
  28. data/lib/vident/internals/plan.rb +9 -0
  29. data/lib/vident/internals/registry.rb +37 -0
  30. data/lib/{vident2 → vident}/internals/resolver.rb +101 -91
  31. data/lib/{vident2 → vident}/internals/target_builder.rb +1 -7
  32. data/lib/vident/stable_id.rb +3 -3
  33. data/lib/{vident2 → vident}/stimulus/action.rb +11 -24
  34. data/lib/vident/stimulus/base.rb +26 -0
  35. data/lib/{vident2 → vident}/stimulus/class_map.rb +6 -18
  36. data/lib/{vident2 → vident}/stimulus/collection.rb +6 -8
  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/{vident2 → vident}/stimulus/outlet.rb +12 -32
  42. data/lib/{vident2 → vident}/stimulus/param.rb +5 -11
  43. data/lib/{vident2 → vident}/stimulus/target.rb +5 -14
  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 +122 -19
  51. data/skills/vident/api-reference.md +259 -115
  52. data/skills/vident/examples.md +23 -10
  53. metadata +38 -60
  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 -106
  57. data/lib/vident/component_class_lists.rb +0 -37
  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 -136
  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
  81. data/lib/vident2/caching.rb +0 -93
  82. data/lib/vident2/component.rb +0 -538
  83. data/lib/vident2/engine.rb +0 -18
  84. data/lib/vident2/error.rb +0 -30
  85. data/lib/vident2/internals/attribute_writer.rb +0 -22
  86. data/lib/vident2/internals/declaration.rb +0 -17
  87. data/lib/vident2/internals/plan.rb +0 -12
  88. data/lib/vident2/internals/registry.rb +0 -41
  89. data/lib/vident2/phlex/html.rb +0 -84
  90. data/lib/vident2/phlex.rb +0 -9
  91. data/lib/vident2/stimulus/controller.rb +0 -59
  92. data/lib/vident2/stimulus/naming.rb +0 -26
  93. data/lib/vident2/stimulus/null.rb +0 -16
  94. data/lib/vident2/stimulus/value.rb +0 -77
  95. data/lib/vident2/tailwind.rb +0 -19
  96. data/lib/vident2/version.rb +0 -5
  97. data/lib/vident2/view_component/base.rb +0 -124
  98. data/lib/vident2/view_component.rb +0 -9
  99. data/lib/vident2.rb +0 -50
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vident
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Ierodiaconou
@@ -86,72 +86,50 @@ files:
86
86
  - lib/generators/vident/install/templates/vident.rb
87
87
  - lib/vident.rb
88
88
  - lib/vident/caching.rb
89
- - lib/vident/child_element_helper.rb
90
- - lib/vident/class_list_builder.rb
89
+ - lib/vident/capabilities/caching.rb
90
+ - lib/vident/capabilities/child_element_rendering.rb
91
+ - lib/vident/capabilities/class_list_building.rb
92
+ - lib/vident/capabilities/declarable.rb
93
+ - lib/vident/capabilities/identifiable.rb
94
+ - lib/vident/capabilities/inspectable.rb
95
+ - lib/vident/capabilities/root_element_rendering.rb
96
+ - lib/vident/capabilities/stimulus_data_emitting.rb
97
+ - lib/vident/capabilities/stimulus_declaring.rb
98
+ - lib/vident/capabilities/stimulus_draft.rb
99
+ - lib/vident/capabilities/stimulus_mutation.rb
100
+ - lib/vident/capabilities/stimulus_parsing.rb
101
+ - lib/vident/capabilities/tailwind.rb
91
102
  - lib/vident/component.rb
92
- - lib/vident/component_attribute_resolver.rb
93
- - lib/vident/component_class_lists.rb
94
103
  - lib/vident/engine.rb
104
+ - lib/vident/error.rb
105
+ - lib/vident/internals/action_builder.rb
106
+ - lib/vident/internals/attribute_writer.rb
107
+ - lib/vident/internals/class_list_builder.rb
108
+ - lib/vident/internals/declaration.rb
109
+ - lib/vident/internals/declarations.rb
110
+ - lib/vident/internals/draft.rb
111
+ - lib/vident/internals/dsl.rb
112
+ - lib/vident/internals/plan.rb
113
+ - lib/vident/internals/registry.rb
114
+ - lib/vident/internals/resolver.rb
115
+ - lib/vident/internals/target_builder.rb
95
116
  - lib/vident/stable_id.rb
96
- - lib/vident/stimulus.rb
117
+ - lib/vident/stimulus/action.rb
118
+ - lib/vident/stimulus/base.rb
119
+ - lib/vident/stimulus/class_map.rb
120
+ - lib/vident/stimulus/collection.rb
121
+ - lib/vident/stimulus/combinable.rb
122
+ - lib/vident/stimulus/controller.rb
97
123
  - lib/vident/stimulus/naming.rb
98
- - lib/vident/stimulus/primitive.rb
99
- - lib/vident/stimulus_action.rb
100
- - lib/vident/stimulus_action_collection.rb
101
- - lib/vident/stimulus_attribute_base.rb
102
- - lib/vident/stimulus_attributes.rb
103
- - lib/vident/stimulus_builder.rb
104
- - lib/vident/stimulus_class.rb
105
- - lib/vident/stimulus_class_collection.rb
106
- - lib/vident/stimulus_collection_base.rb
107
- - lib/vident/stimulus_component.rb
108
- - lib/vident/stimulus_controller.rb
109
- - lib/vident/stimulus_controller_collection.rb
110
- - lib/vident/stimulus_data_attribute_builder.rb
111
- - lib/vident/stimulus_helper.rb
124
+ - lib/vident/stimulus/null.rb
125
+ - lib/vident/stimulus/outlet.rb
126
+ - lib/vident/stimulus/param.rb
127
+ - lib/vident/stimulus/target.rb
128
+ - lib/vident/stimulus/value.rb
112
129
  - lib/vident/stimulus_null.rb
113
- - lib/vident/stimulus_outlet.rb
114
- - lib/vident/stimulus_outlet_collection.rb
115
- - lib/vident/stimulus_param.rb
116
- - lib/vident/stimulus_param_collection.rb
117
- - lib/vident/stimulus_target.rb
118
- - lib/vident/stimulus_target_collection.rb
119
- - lib/vident/stimulus_value.rb
120
- - lib/vident/stimulus_value_collection.rb
121
130
  - lib/vident/tailwind.rb
131
+ - lib/vident/types.rb
122
132
  - lib/vident/version.rb
123
- - lib/vident2.rb
124
- - lib/vident2/caching.rb
125
- - lib/vident2/component.rb
126
- - lib/vident2/engine.rb
127
- - lib/vident2/error.rb
128
- - lib/vident2/internals/action_builder.rb
129
- - lib/vident2/internals/attribute_writer.rb
130
- - lib/vident2/internals/class_list_builder.rb
131
- - lib/vident2/internals/declaration.rb
132
- - lib/vident2/internals/declarations.rb
133
- - lib/vident2/internals/draft.rb
134
- - lib/vident2/internals/dsl.rb
135
- - lib/vident2/internals/plan.rb
136
- - lib/vident2/internals/registry.rb
137
- - lib/vident2/internals/resolver.rb
138
- - lib/vident2/internals/target_builder.rb
139
- - lib/vident2/phlex.rb
140
- - lib/vident2/phlex/html.rb
141
- - lib/vident2/stimulus/action.rb
142
- - lib/vident2/stimulus/class_map.rb
143
- - lib/vident2/stimulus/collection.rb
144
- - lib/vident2/stimulus/controller.rb
145
- - lib/vident2/stimulus/naming.rb
146
- - lib/vident2/stimulus/null.rb
147
- - lib/vident2/stimulus/outlet.rb
148
- - lib/vident2/stimulus/param.rb
149
- - lib/vident2/stimulus/target.rb
150
- - lib/vident2/stimulus/value.rb
151
- - lib/vident2/tailwind.rb
152
- - lib/vident2/version.rb
153
- - lib/vident2/view_component.rb
154
- - lib/vident2/view_component/base.rb
155
133
  - skills/vident/SKILL.md
156
134
  - skills/vident/api-reference.md
157
135
  - skills/vident/examples.md
@@ -1,64 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Vident
4
- module ChildElementHelper
5
- # Explicit kwargs (14 of them, 1 plural + 1 singular per primitive) are the
6
- # public API — keep them so call-sites get typo-checking and IDE support.
7
- # The body is registry-driven via `Stimulus::PRIMITIVES`.
8
- def child_element(
9
- tag_name,
10
- stimulus_controllers: nil,
11
- stimulus_targets: nil,
12
- stimulus_actions: nil,
13
- stimulus_outlets: nil,
14
- stimulus_values: nil,
15
- stimulus_params: nil,
16
- stimulus_classes: nil,
17
- stimulus_controller: nil,
18
- stimulus_target: nil,
19
- stimulus_action: nil,
20
- stimulus_outlet: nil,
21
- stimulus_value: nil,
22
- stimulus_param: nil,
23
- stimulus_class: nil,
24
- **options,
25
- &block
26
- )
27
- inputs = {
28
- controllers: [stimulus_controllers, stimulus_controller],
29
- actions: [stimulus_actions, stimulus_action],
30
- targets: [stimulus_targets, stimulus_target],
31
- outlets: [stimulus_outlets, stimulus_outlet],
32
- values: [stimulus_values, stimulus_value],
33
- params: [stimulus_params, stimulus_param],
34
- classes: [stimulus_classes, stimulus_class]
35
- }
36
-
37
- collections = Stimulus::PRIMITIVES.to_h do |primitive|
38
- plural, singular = inputs.fetch(primitive.name)
39
- child_element_attribute_must_be_collection!(plural, primitive.key.to_s)
40
- args = primitive.keyed? ? [plural || singular] : child_element_wrap_single_stimulus_attribute(plural, singular)
41
- [primitive.name, send(primitive.key, *Array.wrap(args))]
42
- end
43
-
44
- data_attrs = StimulusDataAttributeBuilder.new(**collections).build
45
- generate_child_element(tag_name, data_attrs, options, &block)
46
- end
47
-
48
- private
49
-
50
- def child_element_attribute_must_be_collection!(collection, name)
51
- return unless collection
52
- raise ArgumentError, "'#{name}:' must be an enumerable. Did you mean '#{name.to_s.singularize}:'?" unless collection.is_a?(Enumerable)
53
- end
54
-
55
- def child_element_wrap_single_stimulus_attribute(plural, singular)
56
- return plural if plural
57
- singular.nil? ? nil : [singular]
58
- end
59
-
60
- def generate_child_element(tag_name, stimulus_data_attributes, options, &block)
61
- raise NoMethodError, "Not implemented"
62
- end
63
- end
64
- end
@@ -1,112 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "set"
4
-
5
- module Vident
6
- class ClassListBuilder
7
- CLASSNAME_SEPARATOR = " "
8
-
9
- # If the HTML "class" option is provided, it is taken in order of precedence of source.
10
- # The order of precedence is:
11
- # lowest | root_element_classes => whatever is returned
12
- # ....... | root_element_attributes => the `html_options[:class]` value
13
- # ....... | root_element(class: ...) => the `class` value of the arguments passed to the root element
14
- # highest | render MyComponent.new(html_options: { class: ... }) => the `html_options[:class]` value
15
- # The "classes" prop on the component on the other hand is used to add additional classes to the component.
16
- # eg: render MyComponent.new(classes: "my-additional-class another-class")
17
- def initialize(tailwind_merger: nil, component_name: nil, root_element_attributes_classes: nil, root_element_classes: nil, root_element_html_class: nil, html_class: nil, additional_classes: nil)
18
- @class_list = component_name ? [component_name] : []
19
- @class_list.concat(Array.wrap(root_element_classes)) if root_element_classes && !root_element_attributes_classes && !root_element_html_class && !html_class
20
- @class_list.concat(Array.wrap(root_element_attributes_classes)) if root_element_attributes_classes && !root_element_html_class && !html_class
21
- @class_list.concat(Array.wrap(root_element_html_class)) if root_element_html_class && !html_class
22
- @class_list.concat(Array.wrap(html_class)) if html_class
23
- @class_list.concat(Array.wrap(additional_classes)) if additional_classes
24
- @class_list.compact!
25
-
26
- @tailwind_merger = tailwind_merger
27
-
28
- if @tailwind_merger && !defined?(::TailwindMerge::Merger)
29
- raise LoadError, "TailwindMerge gem is required when using tailwind_merger:. Add 'gem \"tailwind_merge\"' to your Gemfile."
30
- end
31
- end
32
-
33
- # Main method to build a final class list from multiple sources
34
- # @param class_lists [Array<String, Array, StimulusClass, nil>] Multiple class sources to merge
35
- # @param stimulus_class_names [Array<Symbol, String>] Optional names of stimulus classes to include
36
- # @return [String, nil] Final space-separated class string or nil if no classes
37
- def build(extra_classes = nil, stimulus_class_names: [])
38
- class_list = @class_list + Array.wrap(extra_classes).compact
39
- flattened_classes = flatten_and_normalize_classes(class_list, stimulus_class_names)
40
- return nil if flattened_classes.empty?
41
-
42
- deduplicated_classes = dedupe_classes(flattened_classes)
43
- return nil if deduplicated_classes.blank?
44
-
45
- class_string = deduplicated_classes.join(CLASSNAME_SEPARATOR)
46
-
47
- if @tailwind_merger
48
- dedupe_with_tailwind(class_string)
49
- else
50
- class_string
51
- end
52
- end
53
-
54
- private
55
-
56
- # Flatten and normalize all input class sources
57
- def flatten_and_normalize_classes(class_lists, stimulus_class_names)
58
- stimulus_class_names_set = stimulus_class_names.map { |name| name.to_s.dasherize }.to_set
59
-
60
- class_lists.compact.flat_map do |class_source|
61
- case class_source
62
- when String
63
- class_source.split(CLASSNAME_SEPARATOR).reject(&:empty?)
64
- when Array
65
- class_source.flat_map { |item| normalize_single_class_item(item, stimulus_class_names_set) }
66
- else
67
- normalize_single_class_item(class_source, stimulus_class_names_set)
68
- end
69
- end.compact
70
- end
71
-
72
- # Normalize a single class item (could be string, StimulusClass, object with to_s, etc.)
73
- def normalize_single_class_item(item, stimulus_class_names_set)
74
- return [] if item.blank?
75
-
76
- # Handle StimulusClass instances
77
- if stimulus_class_instance?(item)
78
- # Only include if the class name matches one of the requested names
79
- # If stimulus_class_names_set is empty, exclude all stimulus classes
80
- if stimulus_class_names_set.present? && stimulus_class_names_set.include?(item.class_name)
81
- class_value = item.to_s
82
- class_value.include?(CLASSNAME_SEPARATOR) ?
83
- class_value.split(CLASSNAME_SEPARATOR).reject(&:empty?) :
84
- [class_value]
85
- else
86
- []
87
- end
88
- else
89
- # Handle regular strings and other objects
90
- item_string = item.to_s
91
- item_string.include?(CLASSNAME_SEPARATOR) ?
92
- item_string.split(CLASSNAME_SEPARATOR).reject(&:empty?) :
93
- [item_string]
94
- end
95
- end
96
-
97
- # Check if an item is a StimulusClass instance
98
- def stimulus_class_instance?(item)
99
- item.respond_to?(:class_name) && item.respond_to?(:to_s)
100
- end
101
-
102
- # Deduplicate classes while preserving order (first occurrence wins)
103
- def dedupe_classes(class_array)
104
- class_array.reject(&:blank?).uniq
105
- end
106
-
107
- # Merge classes using Tailwind CSS merge
108
- def dedupe_with_tailwind(class_string)
109
- @tailwind_merger.merge(class_string)
110
- end
111
- end
112
- end
@@ -1,106 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Vident
4
- module ComponentAttributeResolver
5
- include Stimulus::Naming
6
-
7
- private
8
-
9
- # Prepare attributes set at initialization. The DSL's static entries are
10
- # merged in here so user-land `after_component_initialize` mutators append
11
- # after them (preserving DSL-first ordering). DSL procs are NOT resolved
12
- # here — they run at render time via `resolve_stimulus_attributes_at_render_time`
13
- # so they can reach `helpers` / `view_context`.
14
- def prepare_component_attributes
15
- prepare_stimulus_collections
16
-
17
- add_stimulus_attributes_from_dsl(phase: :static)
18
-
19
- extra = root_element_attributes
20
- @html_options = (extra[:html_options] || {}).merge(@html_options) if extra.key?(:html_options)
21
- @root_element_attributes_classes = extra[:classes]
22
- @root_element_attributes_id = extra[:id] || id
23
- @element_tag = extra[:element_tag] if extra.key?(:element_tag)
24
-
25
- Stimulus::PRIMITIVES.each do |primitive|
26
- send(mutator_method(primitive), extra[primitive.key]) if extra.key?(primitive.key)
27
- end
28
-
29
- @stimulus_proc_attributes_resolved = false
30
- end
31
-
32
- # Render-phase: resolve DSL proc entries against the component instance,
33
- # now that `helpers` / `view_context` are wired. Triggered by Phlex's
34
- # `before_template` and ViewComponent's `before_render`. Idempotent —
35
- # `stimulus_data_attributes` also calls this as a safety net.
36
- def resolve_stimulus_attributes_at_render_time
37
- return if @stimulus_proc_attributes_resolved
38
- @stimulus_proc_attributes_resolved = true
39
-
40
- add_stimulus_attributes_from_dsl(phase: :procs)
41
- end
42
-
43
- def resolve_root_element_attributes_before_render(root_element_html_options = nil)
44
- extra = root_element_html_options || {}
45
-
46
- # Options set on component at render time take precedence over attributes set by methods on the component
47
- # or attributes passed to root_element in the template
48
- final_attributes = {
49
- data: stimulus_data_attributes # Lowest precedence
50
- }
51
- if root_element_html_options.present? # Mid precedence
52
- root_element_tag_html_options_merge(final_attributes, root_element_html_options)
53
- end
54
- if @html_options.present? # Highest precedence
55
- root_element_tag_html_options_merge(final_attributes, @html_options)
56
- end
57
- final_attributes[:class] = render_classes(extra[:class])
58
- final_attributes[:id] = (extra[:id] || @root_element_attributes_id) unless final_attributes.key?(:id)
59
- final_attributes
60
- end
61
-
62
- def root_element_tag_html_options_merge(final_attributes, other_html_options)
63
- if other_html_options[:data].present?
64
- final_attributes[:data].merge!(other_html_options[:data])
65
- end
66
- final_attributes.merge!(other_html_options.except(:data))
67
- end
68
-
69
- # Run DSL attributes through their `add_stimulus_*` mutators. `phase:` is
70
- # forwarded to the builder: `:static` skips procs (init-time), `:procs`
71
- # skips non-procs (render-time), `:all` resolves everything.
72
- # `values_from_props` is a sidecar on values, resolved at instance
73
- # render time (only during the static phase since it has no procs).
74
- def add_stimulus_attributes_from_dsl(phase: :all)
75
- dsl_attrs = self.class.stimulus_dsl_attributes(self, phase:)
76
- return if dsl_attrs.empty?
77
-
78
- Stimulus::PRIMITIVES.each do |primitive|
79
- value = dsl_attrs[primitive.key]
80
- send(mutator_method(primitive), value) if value
81
- end
82
-
83
- if dsl_attrs[:stimulus_values_from_props]
84
- resolved_values = resolve_values_from_props(dsl_attrs[:stimulus_values_from_props])
85
- add_stimulus_values(resolved_values) unless resolved_values.empty?
86
- end
87
- end
88
-
89
- # Seed the collection ivars from each prop's raw value.
90
- def prepare_stimulus_collections
91
- Stimulus::PRIMITIVES.each do |primitive|
92
- raw = instance_variable_get(prop_ivar(primitive))
93
- collection = send(primitive.key, *Array.wrap(raw))
94
- instance_variable_set(collection_ivar(primitive), collection)
95
- end
96
-
97
- @stimulus_outlet_host.add_stimulus_outlets(self) if @stimulus_outlet_host
98
- end
99
-
100
- def stimulus_data_attributes
101
- resolve_stimulus_attributes_at_render_time
102
- collections = Stimulus::PRIMITIVES.to_h { |primitive| [primitive.name, instance_variable_get(collection_ivar(primitive))] }
103
- StimulusDataAttributeBuilder.new(**collections).build
104
- end
105
- end
106
- end
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Vident
4
- module ComponentClassLists
5
- # Generates the full list of HTML classes for the component
6
- def render_classes(root_element_html_class = nil) = class_list_builder(root_element_html_class).build
7
-
8
- # Getter for a stimulus classes list so can be used in view to set initial state on SSR
9
- # Returns a String of classes that can be used in a `class` attribute.
10
- def class_list_for_stimulus_classes(*names)
11
- # DSL proc entries are resolved lazily at render time; trigger them now
12
- # so procs that use only instance state work from ERB/template.
13
- resolve_stimulus_attributes_at_render_time if respond_to?(:resolve_stimulus_attributes_at_render_time, true)
14
- ClassListBuilder.new(tailwind_merger:).build(
15
- @stimulus_classes_collection&.to_a,
16
- stimulus_class_names: names
17
- ) || ""
18
- end
19
-
20
- private
21
-
22
- # Not memoised: the per-thread TailwindMerger is the only expensive piece
23
- # and it's already cached; the builder itself just copies a few ivars.
24
- # Memoising here would latch the first caller's `root_element_html_class:`.
25
- def class_list_builder(root_element_html_class = nil)
26
- ClassListBuilder.new(
27
- tailwind_merger:,
28
- component_name:,
29
- root_element_attributes_classes: @root_element_attributes_classes,
30
- root_element_classes:,
31
- root_element_html_class:,
32
- additional_classes: @classes,
33
- html_class: @html_options&.fetch(:class, nil)
34
- )
35
- end
36
- end
37
- end
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Vident
4
- module Stimulus
5
- # A Stimulus primitive kind: name, plus the Value/Collection classes that
6
- # back it. Two concrete subclasses distinguish how the primitive behaves
7
- # when a Hash is passed to the plural parser:
8
- #
9
- # - `Keyed` — `{a: 1, b: 2}` expands to one value object per pair.
10
- # Used for values / params / classes / outlets.
11
- # - `Positional` — `{...}` is a single-arg descriptor (e.g. Action's
12
- # `{event:, method:, ...}` short form).
13
- # Used for controllers / actions / targets.
14
- class Primitive < ::Data.define(:name, :value_class, :collection_class)
15
- # Short forms. `name` (Data field) is the plural — `:values`; `plural`
16
- # is an alias for symmetry with `singular`.
17
- alias_method :plural, :name
18
- def singular = name.to_s.singularize.to_sym
19
-
20
- # The primitive's key in Vident's attribute namespace. Used both as the
21
- # parser method name (`def stimulus_values(...)`) and as the hash key
22
- # in DSL attrs / component props / `root_element_attributes` — the same
23
- # Symbol serves all three roles.
24
- def key = :"stimulus_#{name}"
25
- def singular_key = :"stimulus_#{singular}"
26
-
27
- def keyed? = raise NotImplementedError
28
- end
29
-
30
- class KeyedPrimitive < Primitive
31
- def keyed? = true
32
- end
33
-
34
- class PositionalPrimitive < Primitive
35
- def keyed? = false
36
- end
37
- end
38
- end
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Vident
4
- module Stimulus
5
- # Registry of primitive kinds. Add an entry (paired with a Value/Collection
6
- # class pair) to extend; plural parsers, mutators, and the data-attribute
7
- # builder pick it up. Array order = data attribute emission order.
8
- PRIMITIVES = [
9
- PositionalPrimitive.new(:controllers, StimulusController, StimulusControllerCollection),
10
- PositionalPrimitive.new(:actions, StimulusAction, StimulusActionCollection),
11
- PositionalPrimitive.new(:targets, StimulusTarget, StimulusTargetCollection),
12
- KeyedPrimitive.new(:outlets, StimulusOutlet, StimulusOutletCollection),
13
- KeyedPrimitive.new(:values, StimulusValue, StimulusValueCollection),
14
- KeyedPrimitive.new(:params, StimulusParam, StimulusParamCollection),
15
- KeyedPrimitive.new(:classes, StimulusClass, StimulusClassCollection)
16
- ].freeze
17
-
18
- PRIMITIVES_BY_NAME = PRIMITIVES.to_h { |primitive| [primitive.name, primitive] }.freeze
19
-
20
- class << self
21
- def primitive(name)
22
- PRIMITIVES_BY_NAME[name] or
23
- raise ArgumentError, "Unknown stimulus primitive #{name.inspect}; valid: #{PRIMITIVES_BY_NAME.keys.inspect}"
24
- end
25
-
26
- def each(&block) = PRIMITIVES.each(&block)
27
-
28
- def names = PRIMITIVES.map(&:name)
29
- end
30
- end
31
- end
@@ -1,133 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Vident
4
- class StimulusAction < StimulusAttributeBase
5
- # https://stimulus.hotwired.dev/reference/actions#options
6
- VALID_OPTIONS = [:once, :prevent, :stop, :passive, :"!passive", :capture, :self].freeze
7
-
8
- # Typed descriptor for modifiers (`:once`/`:prevent`/etc., keyboard filter,
9
- # `@window`) that the plain Array form can't express. Hash input to the
10
- # parsers (`{event:, method:, ...}`) is desugared into one of these.
11
- class Descriptor < ::Literal::Data
12
- prop :method, _Union(Symbol, String)
13
- prop :event, _Nilable(_Union(Symbol, String)), default: nil
14
- prop :controller, _Nilable(String), default: nil
15
- prop :options, _Array(Symbol), default: -> { [] }
16
- prop :keyboard, _Nilable(String), default: nil
17
- prop :window, _Boolean, default: false
18
- end
19
-
20
- attr_reader :event, :controller, :action, :options, :keyboard, :window
21
-
22
- def initialize(*args, implied_controller: nil)
23
- @options = []
24
- @keyboard = nil
25
- @window = false
26
- super
27
- end
28
-
29
- def to_s
30
- head =
31
- if @event
32
- ev = @event.to_s
33
- ev = "#{ev}.#{@keyboard}" if @keyboard
34
- ev = "#{ev}#{@options.map { |o| ":#{o}" }.join}" if @options.any?
35
- ev = "#{ev}@window" if @window
36
- "#{ev}->"
37
- else
38
- ""
39
- end
40
- "#{head}#{@controller}##{@action}"
41
- end
42
-
43
- def data_attribute_name = "action"
44
-
45
- def data_attribute_value = to_s
46
-
47
- private
48
-
49
- def parse_arguments(*args)
50
- part1, part2, part3 = args
51
-
52
- case args.size
53
- when 1
54
- parse_single_argument(part1)
55
- when 2
56
- parse_two_arguments(part1, part2)
57
- when 3
58
- parse_three_arguments(part1, part2, part3)
59
- else
60
- raise ArgumentError, "Invalid number of 'action' arguments: #{args.size}"
61
- end
62
- end
63
-
64
- def parse_single_argument(arg)
65
- case arg
66
- when Descriptor then apply_descriptor(arg)
67
- when Hash then apply_descriptor(Descriptor.new(**arg))
68
- when Symbol
69
- @event = nil
70
- @controller = implied_controller_name
71
- @action = js_name(arg)
72
- when String then parse_qualified_action_string(arg)
73
- else raise ArgumentError, "Invalid 'action' argument type (1): #{arg.class}"
74
- end
75
- end
76
-
77
- # (:event, :method) or ("controller/path", :method)
78
- def parse_two_arguments(part1, part2)
79
- if part1.is_a?(Symbol) && part2.is_a?(Symbol)
80
- @event = part1.to_s
81
- @controller = implied_controller_name
82
- @action = js_name(part2)
83
- elsif part1.is_a?(String) && part2.is_a?(Symbol)
84
- @event = nil
85
- @controller = stimulize_path(part1)
86
- @action = js_name(part2)
87
- else
88
- raise ArgumentError, "Invalid 'action' argument types (2): #{part1.class}, #{part2.class}"
89
- end
90
- end
91
-
92
- # (:event, "controller/path", :method)
93
- def parse_three_arguments(part1, part2, part3)
94
- if part1.is_a?(Symbol) && part2.is_a?(String) && part3.is_a?(Symbol)
95
- @event = part1.to_s
96
- @controller = stimulize_path(part2)
97
- @action = js_name(part3)
98
- else
99
- raise ArgumentError, "Invalid 'action' argument types (3): #{part1.class}, #{part2.class}, #{part3.class}"
100
- end
101
- end
102
-
103
- def apply_descriptor(d)
104
- invalid = d.options - VALID_OPTIONS
105
- unless invalid.empty?
106
- raise ArgumentError,
107
- "Invalid action option(s) #{invalid.inspect}. Valid: #{VALID_OPTIONS.inspect}"
108
- end
109
-
110
- @event = d.event&.to_s
111
- @controller = d.controller ? stimulize_path(d.controller) : implied_controller_name
112
- @action = d.method.is_a?(Symbol) ? js_name(d.method) : d.method.to_s
113
- @options = d.options
114
- @keyboard = d.keyboard
115
- @window = d.window
116
- end
117
-
118
- def parse_qualified_action_string(action_string)
119
- if action_string.include?("->")
120
- event_part, controller_action = action_string.split("->", 2)
121
- @event = event_part
122
- controller_part, action_part = controller_action.split("#", 2)
123
- @controller = controller_part
124
- @action = action_part
125
- else
126
- @event = nil
127
- controller_part, action_part = action_string.split("#", 2)
128
- @controller = controller_part
129
- @action = action_part
130
- end
131
- end
132
- end
133
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Vident
4
- class StimulusActionCollection < StimulusCollectionBase
5
- def to_h
6
- return {} if items.empty?
7
-
8
- {action: items.map(&:to_s).join(" ")}
9
- end
10
- end
11
- end