vident 1.0.0.beta1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -0
  3. data/README.md +177 -23
  4. data/lib/generators/vident/install/install_generator.rb +53 -0
  5. data/lib/generators/vident/install/templates/vident.rb +20 -0
  6. data/lib/vident/caching.rb +3 -9
  7. data/lib/vident/child_element_helper.rb +64 -0
  8. data/lib/vident/component.rb +4 -11
  9. data/lib/vident/component_attribute_resolver.rb +21 -36
  10. data/lib/vident/component_class_lists.rb +4 -3
  11. data/lib/vident/stable_id.rb +48 -17
  12. data/lib/vident/stimulus/naming.rb +19 -0
  13. data/lib/vident/stimulus/primitive.rb +38 -0
  14. data/lib/vident/stimulus.rb +31 -0
  15. data/lib/vident/stimulus_action.rb +58 -23
  16. data/lib/vident/stimulus_attribute_base.rb +27 -23
  17. data/lib/vident/stimulus_attributes.rb +56 -185
  18. data/lib/vident/stimulus_builder.rb +66 -87
  19. data/lib/vident/stimulus_class.rb +3 -9
  20. data/lib/vident/stimulus_class_collection.rb +1 -5
  21. data/lib/vident/stimulus_collection_base.rb +4 -12
  22. data/lib/vident/stimulus_component.rb +8 -7
  23. data/lib/vident/stimulus_controller.rb +10 -13
  24. data/lib/vident/stimulus_data_attribute_builder.rb +15 -74
  25. data/lib/vident/stimulus_helper.rb +4 -12
  26. data/lib/vident/stimulus_null.rb +21 -0
  27. data/lib/vident/stimulus_outlet.rb +4 -11
  28. data/lib/vident/stimulus_outlet_collection.rb +1 -5
  29. data/lib/vident/stimulus_param.rb +42 -0
  30. data/lib/vident/stimulus_param_collection.rb +11 -0
  31. data/lib/vident/stimulus_target.rb +7 -17
  32. data/lib/vident/stimulus_target_collection.rb +2 -6
  33. data/lib/vident/stimulus_value.rb +14 -44
  34. data/lib/vident/stimulus_value_collection.rb +1 -5
  35. data/lib/vident/tailwind.rb +0 -2
  36. data/lib/vident/version.rb +1 -1
  37. data/lib/vident.rb +8 -13
  38. data/skills/vident/SKILL.md +628 -0
  39. metadata +11 -2
  40. data/lib/vident/tag_helper.rb +0 -65
@@ -1,90 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vident
4
- # Builds a hash of Stimulus data attributes from collections of stimulus objects
5
- # Handles merging multiple actions, targets, outlets, values, and classes
6
- # into the final data-* attributes needed for HTML elements
7
4
  class StimulusDataAttributeBuilder
8
- def initialize(controllers: [], actions: [], targets: [], outlets: [], values: [], classes: [])
9
- @controllers = Array(controllers)
10
- @actions = Array(actions)
11
- @targets = Array(targets)
12
- @outlets = Array(outlets)
13
- @values = Array(values)
14
- @classes = Array(classes)
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) }
15
10
  end
16
11
 
17
- # Build the final data attributes hash
18
12
  def build
19
- {
20
- **merged_controllers,
21
- **merged_actions,
22
- **merged_targets,
23
- **merged_outlets,
24
- **merged_values,
25
- **merged_classes
26
- }.transform_keys(&:to_s).compact
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
27
16
  end
28
17
 
29
18
  private
30
19
 
31
- def merged_controllers
32
- return {} if @controllers.empty?
33
-
34
- if @controllers.first.is_a?(StimulusControllerCollection)
35
- StimulusControllerCollection.merge(*@controllers).to_h
36
- else
37
- StimulusControllerCollection.new(@controllers).to_h
38
- end
39
- end
40
-
41
- def merged_actions
42
- return {} if @actions.empty?
43
-
44
- if @actions.first.is_a?(StimulusActionCollection)
45
- StimulusActionCollection.merge(*@actions).to_h
46
- else
47
- StimulusActionCollection.new(@actions).to_h
48
- end
49
- end
50
-
51
- def merged_targets
52
- return {} if @targets.empty?
53
-
54
- if @targets.first.is_a?(StimulusTargetCollection)
55
- StimulusTargetCollection.merge(*@targets).to_h
56
- else
57
- StimulusTargetCollection.new(@targets).to_h
58
- end
59
- end
60
-
61
- def merged_outlets
62
- return {} if @outlets.empty?
63
-
64
- if @outlets.first.is_a?(StimulusOutletCollection)
65
- StimulusOutletCollection.merge(*@outlets).to_h
66
- else
67
- StimulusOutletCollection.new(@outlets).to_h
68
- end
69
- end
70
-
71
- def merged_values
72
- return {} if @values.empty?
73
-
74
- if @values.first.is_a?(StimulusValueCollection)
75
- StimulusValueCollection.merge(*@values).to_h
76
- else
77
- StimulusValueCollection.new(@values).to_h
78
- end
79
- end
80
-
81
- def merged_classes
82
- return {} if @classes.empty?
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?
83
24
 
84
- if @classes.first.is_a?(StimulusClassCollection)
85
- StimulusClassCollection.merge(*@classes).to_h
25
+ if items.first.is_a?(collection_class)
26
+ collection_class.merge(*items).to_h
86
27
  else
87
- StimulusClassCollection.new(@classes).to_h
28
+ collection_class.new(items).to_h
88
29
  end
89
30
  end
90
31
  end
@@ -47,28 +47,20 @@ module Vident
47
47
 
48
48
  protected
49
49
 
50
- def stimulus_dsl_builder
51
- @stimulus_builder
52
- end
50
+ def stimulus_dsl_builder = @stimulus_builder
53
51
  end
54
52
 
55
53
  # Instance method to get DSL attributes for this component instance
56
- def stimulus_dsl_attributes
57
- self.class.stimulus_dsl_attributes(self)
58
- end
54
+ def stimulus_dsl_attributes = self.class.stimulus_dsl_attributes(self)
59
55
 
60
56
  # Instance method to resolve prop-mapped values at runtime
61
57
  def resolve_values_from_props(prop_names)
62
58
  return {} if prop_names.empty?
63
59
 
64
- resolved = {}
65
- prop_names.each do |name|
60
+ prop_names.each_with_object({}) do |name, resolved|
66
61
  # Map from instance variable if it exists
67
- if instance_variable_defined?("@#{name}")
68
- resolved[name] = instance_variable_get("@#{name}")
69
- end
62
+ resolved[name] = instance_variable_get("@#{name}") if instance_variable_defined?("@#{name}")
70
63
  end
71
- resolved
72
64
  end
73
65
  end
74
66
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vident
4
+ # Sentinel: emits the literal string "null" as the data attribute value.
5
+ # For Stimulus `Object` and `Array` value types this is JSON-parsed to JS `null`;
6
+ # other value types will read it as garbage ("null" string / NaN / truthy), so only
7
+ # use this with nullable Object/Array values.
8
+ #
9
+ # A bare `nil` (static or returned from a proc) omits the attribute entirely so
10
+ # Stimulus uses its per-type default. Reach for this sentinel only when you need
11
+ # an explicit JS `null`.
12
+ StimulusNull = Object.new
13
+ def StimulusNull.inspect
14
+ "Vident::StimulusNull"
15
+ end
16
+
17
+ def StimulusNull.to_s
18
+ "null"
19
+ end
20
+ StimulusNull.freeze
21
+ end
@@ -9,17 +9,11 @@ module Vident
9
9
  super(*args, implied_controller: implied_controller)
10
10
  end
11
11
 
12
- def to_s
13
- @selector
14
- end
12
+ def to_s = @selector
15
13
 
16
- def data_attribute_name
17
- "#{@controller}-#{@outlet_name}-outlet"
18
- end
14
+ def data_attribute_name = "#{@controller}-#{@outlet_name}-outlet"
19
15
 
20
- def data_attribute_value
21
- @selector
22
- end
16
+ def data_attribute_value = @selector
23
17
 
24
18
  private
25
19
 
@@ -67,8 +61,7 @@ module Vident
67
61
  end
68
62
 
69
63
  def parse_two_arguments(arg1, arg2)
70
- if arg1.is_a?(Symbol) && arg2.is_a?(String)
71
- # outlet name on implied controller + custom selector
64
+ if (arg1.is_a?(Symbol) || arg1.is_a?(String)) && arg2.is_a?(String)
72
65
  @controller = implied_controller_name
73
66
  @outlet_name = arg1.to_s.dasherize
74
67
  @selector = arg2
@@ -5,11 +5,7 @@ module Vident
5
5
  def to_h
6
6
  return {} if items.empty?
7
7
 
8
- merged = {}
9
- items.each do |outlet|
10
- merged.merge!(outlet.to_h)
11
- end
12
- merged
8
+ items.each_with_object({}) { |outlet, merged| merged.merge!(outlet.to_h) }
13
9
  end
14
10
  end
15
11
  end
@@ -0,0 +1,42 @@
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
@@ -0,0 +1,11 @@
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
@@ -4,19 +4,13 @@ module Vident
4
4
  class StimulusTarget < StimulusAttributeBase
5
5
  attr_reader :controller, :name
6
6
 
7
- def to_s
8
- @name
9
- end
7
+ def to_s = @name
10
8
 
11
9
  # Returns the data attribute name for this target
12
- def data_attribute_name
13
- "#{@controller}-target"
14
- end
10
+ def data_attribute_name = "#{@controller}-target"
15
11
 
16
12
  # Returns the target name value for the data attribute
17
- def data_attribute_value
18
- @name
19
- end
13
+ def data_attribute_value = @name
20
14
 
21
15
  private
22
16
 
@@ -33,14 +27,10 @@ module Vident
33
27
 
34
28
  def parse_single_argument(arg)
35
29
  @controller = implied_controller_name
36
- if arg.is_a?(Symbol)
37
- # 1 symbol arg, name of target on implied controller
38
- @name = js_name(arg)
39
- elsif arg.is_a?(String)
40
- # 1 string arg, assume it's a target name on implied controller
41
- @name = arg
42
- else
43
- raise ArgumentError, "Invalid argument type: #{arg.class}"
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}"
44
34
  end
45
35
  end
46
36
 
@@ -8,12 +8,8 @@ module Vident
8
8
  merged = {}
9
9
  items.each do |target|
10
10
  target.to_h.each do |key, value|
11
- merged[key] = if merged.key?(key)
12
- # Merge space-separated values for same target attribute
13
- "#{merged[key]} #{value}"
14
- else
15
- value
16
- end
11
+ # Merge space-separated values for same target attribute
12
+ merged[key] = merged.key?(key) ? "#{merged[key]} #{value}" : value
17
13
  end
18
14
  end
19
15
  merged
@@ -1,69 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "json"
4
-
5
3
  module Vident
6
4
  class StimulusValue < StimulusAttributeBase
7
5
  attr_reader :controller, :value_name, :value
8
6
 
9
- def to_s
10
- @value.to_s
11
- end
7
+ def to_s = @value.to_s
12
8
 
13
- def data_attribute_name
14
- "#{@controller}-#{@value_name}-value"
15
- end
9
+ def data_attribute_name = "#{@controller}-#{@value_name}-value"
16
10
 
17
- def data_attribute_value
18
- @value
19
- end
11
+ def data_attribute_value = @value
20
12
 
21
13
  private
22
14
 
23
15
  def parse_arguments(*args)
24
16
  case args.size
25
- when 2
26
- parse_two_arguments(args[0], args[1])
27
- when 3
28
- parse_three_arguments(args[0], args[1], args[2])
29
- else
30
- raise ArgumentError, "Invalid number of arguments: #{args.size} (#{args.inspect}). Did you pass an array of hashes?"
17
+ when 2 then parse_two_arguments(*args)
18
+ when 3 then parse_three_arguments(*args)
19
+ else raise ArgumentError, "Invalid number of arguments: #{args.size} (#{args.inspect}). Did you pass an array of hashes?"
31
20
  end
32
21
  end
33
22
 
34
23
  def parse_two_arguments(value_name, value)
35
- if value_name.is_a?(Symbol)
36
- # value name on implied controller + value
37
- @controller = implied_controller_name
38
- @value_name = value_name.to_s.dasherize
39
- @value = serialize_value(value)
40
- else
41
- raise ArgumentError, "Invalid argument types: #{value_name.class}, #{value.class}"
42
- end
24
+ raise ArgumentError, "Invalid argument types: #{value_name.class}, #{value.class}" unless value_name.is_a?(Symbol)
25
+ @controller = implied_controller_name
26
+ @value_name = value_name.to_s.dasherize
27
+ @value = serialize_value(value)
43
28
  end
44
29
 
45
30
  def parse_three_arguments(controller, value_name, value)
46
- if controller.is_a?(String) && value_name.is_a?(Symbol)
47
- # controller + value name + value
48
- @controller = stimulize_path(controller)
49
- @value_name = value_name.to_s.dasherize
50
- @value = serialize_value(value)
51
- else
31
+ unless controller.is_a?(String) && value_name.is_a?(Symbol)
52
32
  raise ArgumentError, "Invalid argument types: #{controller.class}, #{value_name.class}, #{value.class}"
53
33
  end
54
- end
55
-
56
- def serialize_value(value)
57
- case value
58
- when Array, Hash
59
- value.to_json
60
- when TrueClass, FalseClass
61
- value.to_s
62
- when Numeric
63
- value.to_s
64
- else
65
- value.to_s
66
- end
34
+ @controller = stimulize_path(controller)
35
+ @value_name = value_name.to_s.dasherize
36
+ @value = serialize_value(value)
67
37
  end
68
38
  end
69
39
  end
@@ -5,11 +5,7 @@ module Vident
5
5
  def to_h
6
6
  return {} if items.empty?
7
7
 
8
- merged = {}
9
- items.each do |value|
10
- merged.merge!(value.to_h)
11
- end
12
- merged
8
+ items.each_with_object({}) { |value, merged| merged.merge!(value.to_h) }
13
9
  end
14
10
  end
15
11
  end
@@ -16,8 +16,6 @@ module Vident
16
16
  # Check if TailwindMerge gem is available
17
17
  def tailwind_merge_available?
18
18
  defined?(::TailwindMerge::Merger) && ::TailwindMerge::Merger.respond_to?(:new)
19
- rescue NameError
20
- false
21
19
  end
22
20
  end
23
21
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vident
4
- VERSION = "1.0.0.beta1"
4
+ VERSION = "1.0.0"
5
5
 
6
6
  # Shared version for all vident gems
7
7
  def self.version
data/lib/vident.rb CHANGED
@@ -1,17 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- if ENV["COVERAGE"]
4
- require "simplecov"
5
- SimpleCov.command_name "Vident"
6
- SimpleCov.root File.expand_path("..", __dir__)
7
- SimpleCov.start do
8
- add_filter "/test/"
9
- add_filter "/tmp/"
10
- add_filter "/bin/"
11
- add_filter "/lib/vident/engine.rb"
12
- end
13
- end
14
-
15
3
  require "active_support"
16
4
  require "active_support/concern"
17
5
  require "literal"
@@ -19,12 +7,14 @@ require "literal"
19
7
  require "vident/version"
20
8
  require "vident/tailwind"
21
9
 
10
+ require "vident/stimulus_null"
22
11
  require "vident/stimulus_attribute_base"
23
12
  require "vident/stimulus_controller"
24
13
  require "vident/stimulus_action"
25
14
  require "vident/stimulus_target"
26
15
  require "vident/stimulus_outlet"
27
16
  require "vident/stimulus_value"
17
+ require "vident/stimulus_param"
28
18
  require "vident/stimulus_class"
29
19
 
30
20
  require "vident/stimulus_collection_base"
@@ -33,12 +23,17 @@ require "vident/stimulus_action_collection"
33
23
  require "vident/stimulus_target_collection"
34
24
  require "vident/stimulus_outlet_collection"
35
25
  require "vident/stimulus_value_collection"
26
+ require "vident/stimulus_param_collection"
36
27
  require "vident/stimulus_class_collection"
37
28
 
29
+ require "vident/stimulus/primitive"
30
+ require "vident/stimulus/naming"
31
+ require "vident/stimulus"
32
+
38
33
  require "vident/stimulus_attributes"
39
34
  require "vident/stimulus_data_attribute_builder"
40
35
 
41
- require "vident/tag_helper"
36
+ require "vident/child_element_helper"
42
37
  require "vident/stable_id"
43
38
  require "vident/class_list_builder"
44
39