vident 1.0.2 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +50 -0
- data/README.md +45 -17
- data/lib/vident/caching.rb +4 -110
- data/lib/vident/capabilities/caching.rb +98 -0
- data/lib/vident/capabilities/child_element_rendering.rb +92 -0
- data/lib/vident/capabilities/class_list_building.rb +23 -0
- data/lib/vident/capabilities/declarable.rb +39 -0
- data/lib/vident/capabilities/identifiable.rb +54 -0
- data/lib/vident/capabilities/inspectable.rb +17 -0
- data/lib/vident/capabilities/root_element_rendering.rb +31 -0
- data/lib/vident/capabilities/stimulus_data_emitting.rb +98 -0
- data/lib/vident/capabilities/stimulus_declaring.rb +79 -0
- data/lib/vident/capabilities/stimulus_draft.rb +51 -0
- data/lib/vident/capabilities/stimulus_mutation.rb +60 -0
- data/lib/vident/capabilities/stimulus_parsing.rb +144 -0
- data/lib/vident/capabilities/tailwind.rb +18 -0
- data/lib/vident/component.rb +14 -76
- data/lib/vident/engine.rb +6 -5
- data/lib/vident/error.rb +16 -0
- data/lib/{vident2 → vident}/internals/action_builder.rb +18 -22
- data/lib/vident/internals/attribute_writer.rb +17 -0
- data/lib/{vident2 → vident}/internals/class_list_builder.rb +5 -22
- data/lib/vident/internals/declaration.rb +13 -0
- data/lib/{vident2 → vident}/internals/declarations.rb +6 -18
- data/lib/{vident2 → vident}/internals/draft.rb +3 -16
- data/lib/{vident2 → vident}/internals/dsl.rb +6 -32
- data/lib/vident/internals/plan.rb +9 -0
- data/lib/vident/internals/registry.rb +37 -0
- data/lib/{vident2 → vident}/internals/resolver.rb +101 -91
- data/lib/{vident2 → vident}/internals/target_builder.rb +1 -7
- data/lib/vident/stable_id.rb +3 -3
- data/lib/{vident2 → vident}/stimulus/action.rb +11 -24
- data/lib/vident/stimulus/base.rb +26 -0
- data/lib/{vident2 → vident}/stimulus/class_map.rb +6 -18
- data/lib/{vident2 → vident}/stimulus/collection.rb +6 -8
- data/lib/vident/stimulus/combinable.rb +30 -0
- data/lib/vident/stimulus/controller.rb +45 -0
- data/lib/vident/stimulus/naming.rb +9 -9
- data/lib/vident/stimulus/null.rb +7 -0
- data/lib/{vident2 → vident}/stimulus/outlet.rb +12 -32
- data/lib/{vident2 → vident}/stimulus/param.rb +5 -11
- data/lib/{vident2 → vident}/stimulus/target.rb +5 -14
- data/lib/vident/stimulus/value.rb +57 -0
- data/lib/vident/stimulus_null.rb +4 -8
- data/lib/vident/tailwind.rb +4 -17
- data/lib/vident/types.rb +28 -0
- data/lib/vident/version.rb +1 -6
- data/lib/vident.rb +46 -36
- data/skills/vident/SKILL.md +122 -19
- data/skills/vident/api-reference.md +259 -115
- data/skills/vident/examples.md +23 -10
- metadata +38 -60
- data/lib/vident/child_element_helper.rb +0 -64
- data/lib/vident/class_list_builder.rb +0 -112
- data/lib/vident/component_attribute_resolver.rb +0 -106
- data/lib/vident/component_class_lists.rb +0 -37
- data/lib/vident/stimulus/primitive.rb +0 -38
- data/lib/vident/stimulus.rb +0 -31
- data/lib/vident/stimulus_action.rb +0 -133
- data/lib/vident/stimulus_action_collection.rb +0 -11
- data/lib/vident/stimulus_attribute_base.rb +0 -67
- data/lib/vident/stimulus_attributes.rb +0 -129
- data/lib/vident/stimulus_builder.rb +0 -136
- data/lib/vident/stimulus_class.rb +0 -59
- data/lib/vident/stimulus_class_collection.rb +0 -11
- data/lib/vident/stimulus_collection_base.rb +0 -51
- data/lib/vident/stimulus_component.rb +0 -75
- data/lib/vident/stimulus_controller.rb +0 -41
- data/lib/vident/stimulus_controller_collection.rb +0 -14
- data/lib/vident/stimulus_data_attribute_builder.rb +0 -32
- data/lib/vident/stimulus_helper.rb +0 -66
- data/lib/vident/stimulus_outlet.rb +0 -90
- data/lib/vident/stimulus_outlet_collection.rb +0 -11
- data/lib/vident/stimulus_param.rb +0 -42
- data/lib/vident/stimulus_param_collection.rb +0 -11
- data/lib/vident/stimulus_target.rb +0 -47
- data/lib/vident/stimulus_target_collection.rb +0 -18
- data/lib/vident/stimulus_value.rb +0 -39
- data/lib/vident/stimulus_value_collection.rb +0 -11
- data/lib/vident2/caching.rb +0 -93
- data/lib/vident2/component.rb +0 -538
- data/lib/vident2/engine.rb +0 -18
- data/lib/vident2/error.rb +0 -30
- data/lib/vident2/internals/attribute_writer.rb +0 -22
- data/lib/vident2/internals/declaration.rb +0 -17
- data/lib/vident2/internals/plan.rb +0 -12
- data/lib/vident2/internals/registry.rb +0 -41
- data/lib/vident2/phlex/html.rb +0 -84
- data/lib/vident2/phlex.rb +0 -9
- data/lib/vident2/stimulus/controller.rb +0 -59
- data/lib/vident2/stimulus/naming.rb +0 -26
- data/lib/vident2/stimulus/null.rb +0 -16
- data/lib/vident2/stimulus/value.rb +0 -77
- data/lib/vident2/tailwind.rb +0 -19
- data/lib/vident2/version.rb +0 -5
- data/lib/vident2/view_component/base.rb +0 -124
- data/lib/vident2/view_component.rb +0 -9
- data/lib/vident2.rb +0 -50
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "active_support/core_ext/string/inflections"
|
|
4
|
-
|
|
5
|
-
require "json"
|
|
6
|
-
|
|
7
|
-
module Vident
|
|
8
|
-
class StimulusAttributeBase
|
|
9
|
-
# `"admin/users"` → `"admin--users"`; accepts Symbol or String.
|
|
10
|
-
def self.stimulize_path(path)
|
|
11
|
-
path.to_s.split("/").map(&:dasherize).join("--")
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
# `:my_thing` → `"myThing"`
|
|
15
|
-
def self.js_name(name)
|
|
16
|
-
name.to_s.camelize(:lower)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
attr_reader :implied_controller
|
|
20
|
-
|
|
21
|
-
def initialize(*args, implied_controller: nil)
|
|
22
|
-
@implied_controller = implied_controller
|
|
23
|
-
parse_arguments(*args)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def inspect = "#<#{self.class.name} #{to_h}>"
|
|
27
|
-
|
|
28
|
-
def to_s = raise(NoMethodError, "Subclasses must implement to_s")
|
|
29
|
-
|
|
30
|
-
def to_h = {data_attribute_name => data_attribute_value}
|
|
31
|
-
|
|
32
|
-
alias_method :to_hash, :to_h
|
|
33
|
-
|
|
34
|
-
def data_attribute_name = raise(NoMethodError, "Subclasses must implement data_attribute_name")
|
|
35
|
-
|
|
36
|
-
def data_attribute_value = raise(NoMethodError, "Subclasses must implement data_attribute_value")
|
|
37
|
-
|
|
38
|
-
def implied_controller_path
|
|
39
|
-
raise ArgumentError, "implied_controller is required to get implied controller path" unless implied_controller
|
|
40
|
-
implied_controller.path
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def implied_controller_name
|
|
44
|
-
raise ArgumentError, "implied_controller is required to get implied controller name" unless implied_controller
|
|
45
|
-
implied_controller.name
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
private
|
|
49
|
-
|
|
50
|
-
def stimulize_path(path) = self.class.stimulize_path(path)
|
|
51
|
-
|
|
52
|
-
def js_name(name) = self.class.js_name(name)
|
|
53
|
-
|
|
54
|
-
# Arrays/Hashes serialise as JSON; everything else via `to_s` (which is how
|
|
55
|
-
# `Vident::StimulusNull` emits the literal `"null"`).
|
|
56
|
-
def serialize_value(value)
|
|
57
|
-
case value
|
|
58
|
-
when Array, Hash then value.to_json
|
|
59
|
-
else value.to_s
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def parse_arguments(*args)
|
|
64
|
-
raise NotImplementedError, "Subclasses must implement parse_arguments"
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Vident
|
|
4
|
-
module StimulusAttributes
|
|
5
|
-
extend ActiveSupport::Concern
|
|
6
|
-
# `extend` + `include` so Naming helpers are callable both in the module
|
|
7
|
-
# body (outside define_method args) and inside define_method blocks
|
|
8
|
-
# (at instance call-time).
|
|
9
|
-
extend Stimulus::Naming
|
|
10
|
-
include Stimulus::Naming
|
|
11
|
-
|
|
12
|
-
class_methods do
|
|
13
|
-
# Symbol so the action parser treats it as a Stimulus event type.
|
|
14
|
-
def stimulus_scoped_event(event)
|
|
15
|
-
:"#{component_name}:#{stimulus_js_name(event)}"
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def stimulus_scoped_event_on_window(event)
|
|
19
|
-
:"#{component_name}:#{stimulus_js_name(event)}@window"
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
private
|
|
23
|
-
|
|
24
|
-
def stimulus_js_name(name) = name.to_s.camelize(:lower)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def stimulus_controller(*args)
|
|
28
|
-
return args.first if args.length == 1 && args.first.is_a?(StimulusController)
|
|
29
|
-
StimulusController.new(*args, implied_controller: implied_controller_path)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
# Plural parsers `stimulus_<kind>s(*args)` — generated from the primitives
|
|
33
|
-
# registry below. Each accepts: pre-built Value (pass-through), pre-built
|
|
34
|
-
# Collection (unwrapped; a single one is returned as-is), Array (splatted
|
|
35
|
-
# into the singular builder), Hash (expanded per-pair for
|
|
36
|
-
# `hash_expands: true`, single-arg descriptor otherwise), else passed to
|
|
37
|
-
# the singular builder. Methods defined this way: `stimulus_controllers`,
|
|
38
|
-
# `stimulus_actions`, `stimulus_targets`, `stimulus_outlets`,
|
|
39
|
-
# `stimulus_values`, `stimulus_params`, `stimulus_classes`.
|
|
40
|
-
Stimulus::PRIMITIVES.each do |primitive|
|
|
41
|
-
define_method(primitive.key) do |*args|
|
|
42
|
-
collection_class = primitive.collection_class
|
|
43
|
-
return collection_class.new if args.empty? || args.all?(&:blank?)
|
|
44
|
-
return args.first if args.length == 1 && args.first.is_a?(collection_class)
|
|
45
|
-
|
|
46
|
-
singular = primitive.singular_key
|
|
47
|
-
converted = []
|
|
48
|
-
args.each do |arg|
|
|
49
|
-
case arg
|
|
50
|
-
when primitive.value_class then converted << arg
|
|
51
|
-
when collection_class then converted.concat(arg.to_a)
|
|
52
|
-
when Hash
|
|
53
|
-
if primitive.keyed?
|
|
54
|
-
arg.each { |name, val| converted << send(singular, name, val) }
|
|
55
|
-
else
|
|
56
|
-
converted << send(singular, arg)
|
|
57
|
-
end
|
|
58
|
-
when Array then converted << send(singular, *arg)
|
|
59
|
-
else converted << send(singular, arg)
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
collection_class.new(converted)
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def stimulus_action(*args)
|
|
67
|
-
return args.first if args.length == 1 && args.first.is_a?(StimulusAction)
|
|
68
|
-
StimulusAction.new(*args, implied_controller:)
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def stimulus_target(*args)
|
|
72
|
-
return args.first if args.length == 1 && args.first.is_a?(StimulusTarget)
|
|
73
|
-
StimulusTarget.new(*args, implied_controller:)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
# `component_id: @id` scopes the auto-generated selector to this component
|
|
77
|
-
# instance (e.g. `#<host-id> [data-controller~=<outlet>]`).
|
|
78
|
-
def stimulus_outlet(*args)
|
|
79
|
-
return args.first if args.length == 1 && args.first.is_a?(StimulusOutlet)
|
|
80
|
-
StimulusOutlet.new(*args, implied_controller:, component_id: @id)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def stimulus_value(*args)
|
|
84
|
-
return args.first if args.length == 1 && args.first.is_a?(StimulusValue)
|
|
85
|
-
StimulusValue.new(*args, implied_controller:)
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def stimulus_param(*args)
|
|
89
|
-
return args.first if args.length == 1 && args.first.is_a?(StimulusParam)
|
|
90
|
-
StimulusParam.new(*args, implied_controller:)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def stimulus_class(*args)
|
|
94
|
-
return args.first if args.length == 1 && args.first.is_a?(StimulusClass)
|
|
95
|
-
StimulusClass.new(*args, implied_controller:)
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
# Mutators `add_stimulus_<kind>s` — build from input, merge into the
|
|
99
|
-
# per-kind collection ivar. Methods defined: `add_stimulus_controllers`,
|
|
100
|
-
# `add_stimulus_actions`, `add_stimulus_targets`, `add_stimulus_outlets`,
|
|
101
|
-
# `add_stimulus_values`, `add_stimulus_params`, `add_stimulus_classes`.
|
|
102
|
-
Stimulus::PRIMITIVES.each do |primitive|
|
|
103
|
-
define_method(mutator_method(primitive)) do |input|
|
|
104
|
-
added = send(primitive.key, *Array.wrap(input))
|
|
105
|
-
existing = instance_variable_get(collection_ivar(primitive))
|
|
106
|
-
instance_variable_set(collection_ivar(primitive), existing ? existing.merge(added) : added)
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def stimulus_scoped_event(event) = self.class.stimulus_scoped_event(event)
|
|
111
|
-
|
|
112
|
-
def stimulus_scoped_event_on_window(event) = self.class.stimulus_scoped_event_on_window(event)
|
|
113
|
-
|
|
114
|
-
private
|
|
115
|
-
|
|
116
|
-
def implied_controller
|
|
117
|
-
StimulusController.new(implied_controller: implied_controller_path)
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# The first registered controller path becomes the implied controller for
|
|
121
|
-
# unqualified DSL entries (e.g. `actions :click` → `implied#click`).
|
|
122
|
-
def implied_controller_path
|
|
123
|
-
return @implied_controller_path if defined?(@implied_controller_path)
|
|
124
|
-
path = Array.wrap(@stimulus_controllers).first
|
|
125
|
-
raise(StandardError, "No controllers have been specified") unless path
|
|
126
|
-
@implied_controller_path = path
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
end
|
|
@@ -1,136 +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
|
-
# `phase:` controls which entries are resolved.
|
|
66
|
-
# - `:static` — resolve non-proc entries only; procs are skipped entirely
|
|
67
|
-
# (no placeholder). Used at init time, before helpers/view_context exist.
|
|
68
|
-
# - `:procs` — resolve proc entries only; non-procs are skipped. Used at
|
|
69
|
-
# render time so procs can reach `helpers` / `view_context`.
|
|
70
|
-
# - `:all` — resolve everything (legacy path, retained for safety).
|
|
71
|
-
def to_attributes(component_instance, phase: :all)
|
|
72
|
-
attrs = {}
|
|
73
|
-
DSL_PRIMITIVES.each do |primitive|
|
|
74
|
-
entries = @entries[primitive.name]
|
|
75
|
-
next if entries.empty?
|
|
76
|
-
resolved = resolve_entries(primitive, entries, component_instance, phase:)
|
|
77
|
-
attrs[primitive.key] = resolved unless resolved.nil? || resolved.empty?
|
|
78
|
-
end
|
|
79
|
-
if phase != :procs && !@values_from_props.empty?
|
|
80
|
-
attrs[:stimulus_values_from_props] = @values_from_props.dup
|
|
81
|
-
end
|
|
82
|
-
attrs
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def to_hash(component_instance) = to_attributes(component_instance)
|
|
86
|
-
alias_method :to_h, :to_hash
|
|
87
|
-
|
|
88
|
-
protected
|
|
89
|
-
|
|
90
|
-
def entries_for(name) = @entries[name]
|
|
91
|
-
|
|
92
|
-
def values_from_props_list = @values_from_props
|
|
93
|
-
|
|
94
|
-
private
|
|
95
|
-
|
|
96
|
-
# Outlets don't support procs — static merge only. The other keyed kinds
|
|
97
|
-
# and the positional (Array-shaped) kinds resolve procs in the component
|
|
98
|
-
# instance and drop nil results.
|
|
99
|
-
def resolve_entries(primitive, entries, component_instance, phase:)
|
|
100
|
-
if primitive.name == :outlets
|
|
101
|
-
return (phase == :procs) ? {} : entries.dup
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
if primitive.keyed?
|
|
105
|
-
resolve_hash_filtering_nil(entries, component_instance, phase:)
|
|
106
|
-
else
|
|
107
|
-
resolve_array_filtering_nil(entries, component_instance, phase:)
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
def resolve_array_filtering_nil(array, component_instance, phase:)
|
|
112
|
-
array.each_with_object([]) do |value, out|
|
|
113
|
-
is_proc = callable?(value)
|
|
114
|
-
next if phase == :static && is_proc
|
|
115
|
-
next if phase == :procs && !is_proc
|
|
116
|
-
resolved = is_proc ? component_instance.instance_exec(&value) : value
|
|
117
|
-
out << resolved unless resolved.nil?
|
|
118
|
-
end
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
# Dropping nil matters because Stimulus's Boolean value parser reads an
|
|
122
|
-
# empty data attribute as `true` — so `-> { flag? || nil }` would silently
|
|
123
|
-
# flip a Boolean value on. Omitting the entry keeps the attribute off.
|
|
124
|
-
def resolve_hash_filtering_nil(hash, component_instance, phase:)
|
|
125
|
-
hash.each_with_object({}) do |(key, value), out|
|
|
126
|
-
is_proc = callable?(value)
|
|
127
|
-
next if phase == :static && is_proc
|
|
128
|
-
next if phase == :procs && !is_proc
|
|
129
|
-
resolved = is_proc ? component_instance.instance_exec(&value) : value
|
|
130
|
-
out[key] = resolved unless resolved.nil?
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def callable?(value) = value.respond_to?(:call)
|
|
135
|
-
end
|
|
136
|
-
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, phase: :all)
|
|
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, phase:)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
# Ensure inheritance is applied at access time
|
|
29
|
-
ensure_inheritance_merged
|
|
30
|
-
|
|
31
|
-
@stimulus_builder&.to_attributes(component_instance, phase:) || {}
|
|
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(phase: :all) = self.class.stimulus_dsl_attributes(self, phase:)
|
|
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
|