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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +43 -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 +44 -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
|
@@ -3,26 +3,11 @@
|
|
|
3
3
|
require_relative "registry"
|
|
4
4
|
require_relative "draft"
|
|
5
5
|
|
|
6
|
-
module
|
|
6
|
+
module Vident
|
|
7
7
|
module Internals
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# `no_stimulus_controller`); prop and root_element_attributes paths
|
|
12
|
-
# both APPEND.
|
|
13
|
-
#
|
|
14
|
-
# Two entry points:
|
|
15
|
-
# `call` — pure; returns a new Draft. Use `:static` or `:all`.
|
|
16
|
-
# `resolve_procs_into` — mutates an existing Draft. Called at render
|
|
17
|
-
# time so DSL procs can reach `helpers` /
|
|
18
|
-
# `view_context` (not wired at `after_initialize`).
|
|
19
|
-
#
|
|
20
|
-
# Phases: `:static` skips anything with a `when_proc` or top-level Proc
|
|
21
|
-
# in args; `:procs` processes only those; `:all` does everything.
|
|
22
|
-
#
|
|
23
|
-
# Procs nested inside a Hash descriptor (`action(method: -> { ... })`)
|
|
24
|
-
# escape the gate — unsupported shape; use the fluent builder or a
|
|
25
|
-
# top-level Proc.
|
|
8
|
+
# Resolves raw Declarations and instance state into a Draft of typed Stimulus values.
|
|
9
|
+
# Phase `:static` skips proc-bearing entries; `:procs` processes only those; `:all` does both.
|
|
10
|
+
# Procs nested inside a Hash descriptor escape the phase gate — use the fluent builder instead.
|
|
26
11
|
module Resolver
|
|
27
12
|
module_function
|
|
28
13
|
|
|
@@ -31,28 +16,41 @@ module Vident2
|
|
|
31
16
|
|
|
32
17
|
draft = Draft.new
|
|
33
18
|
implied = build_implied_controller(instance)
|
|
19
|
+
alias_map = build_alias_map(declarations)
|
|
34
20
|
|
|
35
21
|
seed_implied_controller(draft, instance)
|
|
36
|
-
resolve_declarations(draft, declarations, instance, implied, phase:
|
|
37
|
-
absorb_stimulus_props(draft, instance, implied, phase:
|
|
38
|
-
absorb_root_element_attributes(draft, instance, implied, phase:
|
|
22
|
+
resolve_declarations(draft, declarations, instance, implied, phase:, alias_map:)
|
|
23
|
+
absorb_stimulus_props(draft, instance, implied, phase:, alias_map:)
|
|
24
|
+
absorb_root_element_attributes(draft, instance, implied, phase:, alias_map:)
|
|
39
25
|
|
|
40
26
|
draft
|
|
41
27
|
end
|
|
42
28
|
|
|
43
|
-
# Caller owns idempotence (Component uses `@__vident2_procs_resolved`).
|
|
44
29
|
def resolve_procs_into(draft, declarations, instance)
|
|
45
30
|
implied = build_implied_controller(instance)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
31
|
+
alias_map = build_alias_map(declarations)
|
|
32
|
+
resolve_declarations(draft, declarations, instance, implied, phase: :procs, alias_map:)
|
|
33
|
+
absorb_stimulus_props(draft, instance, implied, phase: :procs, alias_map:)
|
|
34
|
+
absorb_root_element_attributes(draft, instance, implied, phase: :procs, alias_map:)
|
|
49
35
|
draft
|
|
50
36
|
end
|
|
51
37
|
|
|
38
|
+
def build_alias_map(declarations)
|
|
39
|
+
map = {}
|
|
40
|
+
declarations.controllers.each do |decl|
|
|
41
|
+
alias_name = decl.meta[:as]
|
|
42
|
+
next unless alias_name
|
|
43
|
+
raw_path = decl.args.first
|
|
44
|
+
next if raw_path.nil?
|
|
45
|
+
map[alias_name] = raw_path.to_s
|
|
46
|
+
end
|
|
47
|
+
map
|
|
48
|
+
end
|
|
49
|
+
|
|
52
50
|
def build_implied_controller(instance)
|
|
53
51
|
path = instance.class.stimulus_identifier_path
|
|
54
52
|
name = instance.class.stimulus_identifier
|
|
55
|
-
::
|
|
53
|
+
::Vident::Stimulus::Controller.new(path: path, name: name)
|
|
56
54
|
end
|
|
57
55
|
|
|
58
56
|
def seed_implied_controller(draft, instance)
|
|
@@ -60,27 +58,40 @@ module Vident2
|
|
|
60
58
|
draft.add_controllers(build_implied_controller(instance))
|
|
61
59
|
end
|
|
62
60
|
|
|
63
|
-
def resolve_declarations(draft, declarations, instance, implied, phase:)
|
|
64
|
-
resolve_positional(draft, :controllers, declarations.controllers, instance, phase:
|
|
65
|
-
::
|
|
61
|
+
def resolve_declarations(draft, declarations, instance, implied, phase:, alias_map: {})
|
|
62
|
+
resolve_positional(draft, :controllers, declarations.controllers, instance, phase:) do |args, meta, _inst|
|
|
63
|
+
::Vident::Stimulus::Controller.parse(*args, implied: implied, **meta_for_controller(meta))
|
|
66
64
|
end
|
|
67
65
|
|
|
68
|
-
resolve_positional(draft, :actions, declarations.actions, instance, phase:
|
|
69
|
-
parse_single(::
|
|
66
|
+
resolve_positional(draft, :actions, declarations.actions, instance, phase:) do |args, _meta, _inst|
|
|
67
|
+
parse_single(::Vident::Stimulus::Action, resolve_action_aliases(args, alias_map), implied: implied, component_id: instance_id(instance))
|
|
70
68
|
end
|
|
71
69
|
|
|
72
|
-
resolve_positional(draft, :targets, declarations.targets, instance, phase:
|
|
73
|
-
parse_single(::
|
|
70
|
+
resolve_positional(draft, :targets, declarations.targets, instance, phase:) do |args, _meta, _inst|
|
|
71
|
+
parse_single(::Vident::Stimulus::Target, args, implied: implied, component_id: instance_id(instance))
|
|
74
72
|
end
|
|
75
73
|
|
|
76
|
-
resolve_keyed(draft, :outlets, declarations.outlets, instance, phase:
|
|
74
|
+
resolve_keyed(draft, :outlets, declarations.outlets, instance, phase:) do |key, args, _meta|
|
|
77
75
|
parsed_args = [key_for_parse(key), *args]
|
|
78
|
-
parse_single(::
|
|
76
|
+
parse_single(::Vident::Stimulus::Outlet, parsed_args, implied: implied, component_id: instance_id(instance))
|
|
79
77
|
end
|
|
80
78
|
|
|
81
|
-
resolve_keyed_values(draft, declarations, instance, implied, phase:
|
|
82
|
-
resolve_keyed_scalars(draft, :params, declarations.params, instance, implied, ::
|
|
83
|
-
resolve_keyed_scalars(draft, :class_maps, declarations.class_maps, instance, implied, ::
|
|
79
|
+
resolve_keyed_values(draft, declarations, instance, implied, phase:)
|
|
80
|
+
resolve_keyed_scalars(draft, :params, declarations.params, instance, implied, ::Vident::Stimulus::Param, phase:)
|
|
81
|
+
resolve_keyed_scalars(draft, :class_maps, declarations.class_maps, instance, implied, ::Vident::Stimulus::ClassMap, phase:)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Unknown aliases raise — a symbolic controller ref is declared intent, not a guess.
|
|
85
|
+
def resolve_action_aliases(args, alias_map)
|
|
86
|
+
return args if alias_map.empty?
|
|
87
|
+
args.map do |arg|
|
|
88
|
+
next arg unless arg.is_a?(Hash) && arg[:controller].is_a?(Symbol)
|
|
89
|
+
sym = arg[:controller]
|
|
90
|
+
unless alias_map.key?(sym)
|
|
91
|
+
raise ::Vident::DeclarationError, "Unknown controller alias :#{sym} in action. Declared aliases: #{alias_map.keys.inspect}"
|
|
92
|
+
end
|
|
93
|
+
arg.merge(controller: alias_map[sym])
|
|
94
|
+
end
|
|
84
95
|
end
|
|
85
96
|
|
|
86
97
|
def resolve_positional(draft, kind, entries, instance, phase:)
|
|
@@ -95,8 +106,6 @@ module Vident2
|
|
|
95
106
|
end
|
|
96
107
|
end
|
|
97
108
|
|
|
98
|
-
# If `args` is a single Array element, unwrap it — positional kinds
|
|
99
|
-
# treat Array values as the singular parser's arg tuple.
|
|
100
109
|
def splat_single_array(args)
|
|
101
110
|
(args.size == 1 && args[0].is_a?(Array)) ? args[0] : args
|
|
102
111
|
end
|
|
@@ -120,22 +129,21 @@ module Vident2
|
|
|
120
129
|
if decl.meta[:from_prop]
|
|
121
130
|
raw = read_prop(instance, key)
|
|
122
131
|
next if raw.nil?
|
|
123
|
-
draft.add_values(::
|
|
132
|
+
draft.add_values(::Vident::Stimulus::Value.parse(key, raw, implied: implied))
|
|
124
133
|
next
|
|
125
134
|
end
|
|
126
135
|
|
|
127
136
|
raw = resolve_value_meta(decl, instance)
|
|
128
137
|
next if raw.nil?
|
|
129
|
-
draft.add_values(::
|
|
138
|
+
draft.add_values(::Vident::Stimulus::Value.parse(key, raw, implied: implied))
|
|
130
139
|
end
|
|
131
140
|
|
|
132
|
-
# values_from_props
|
|
133
|
-
# phase_matches? doesn't apply). Ivar reads only; run once.
|
|
141
|
+
# values_from_props has no when_proc, so phase_matches? doesn't apply; skip on :procs pass.
|
|
134
142
|
return if phase == :procs
|
|
135
143
|
declarations.values_from_props.each do |name|
|
|
136
144
|
raw = read_prop(instance, name)
|
|
137
145
|
next if raw.nil?
|
|
138
|
-
draft.add_values(::
|
|
146
|
+
draft.add_values(::Vident::Stimulus::Value.parse(name, raw, implied: implied))
|
|
139
147
|
end
|
|
140
148
|
end
|
|
141
149
|
|
|
@@ -149,95 +157,103 @@ module Vident2
|
|
|
149
157
|
end
|
|
150
158
|
end
|
|
151
159
|
|
|
152
|
-
# Declaration-level phase gate. Nested Procs inside Hash args
|
|
153
|
-
# aren't inspected — see module docstring.
|
|
154
160
|
def phase_matches?(decl, phase)
|
|
155
161
|
return true if phase == :all
|
|
156
162
|
has_proc = decl.when_proc || decl.args.any? { |a| a.is_a?(Proc) }
|
|
157
163
|
(phase == :procs) ? has_proc : !has_proc
|
|
158
164
|
end
|
|
159
165
|
|
|
160
|
-
def absorb_stimulus_props(draft, instance, implied, phase:)
|
|
161
|
-
absorb_input(draft, :controllers, instance_ivar(instance, :@stimulus_controllers), instance, implied, phase:
|
|
162
|
-
absorb_input(draft, :actions,
|
|
163
|
-
absorb_input(draft, :targets,
|
|
164
|
-
absorb_input(draft, :outlets,
|
|
165
|
-
absorb_input(draft, :values,
|
|
166
|
-
absorb_input(draft, :params,
|
|
167
|
-
absorb_input(draft, :class_maps,
|
|
166
|
+
def absorb_stimulus_props(draft, instance, implied, phase:, alias_map: {})
|
|
167
|
+
absorb_input(draft, :controllers, instance_ivar(instance, :@stimulus_controllers), instance, implied, phase:, alias_map:)
|
|
168
|
+
absorb_input(draft, :actions, instance_ivar(instance, :@stimulus_actions), instance, implied, phase:, alias_map:)
|
|
169
|
+
absorb_input(draft, :targets, instance_ivar(instance, :@stimulus_targets), instance, implied, phase:, alias_map:)
|
|
170
|
+
absorb_input(draft, :outlets, instance_ivar(instance, :@stimulus_outlets), instance, implied, phase:, alias_map:)
|
|
171
|
+
absorb_input(draft, :values, instance_ivar(instance, :@stimulus_values), instance, implied, phase:, alias_map:)
|
|
172
|
+
absorb_input(draft, :params, instance_ivar(instance, :@stimulus_params), instance, implied, phase:, alias_map:)
|
|
173
|
+
absorb_input(draft, :class_maps, instance_ivar(instance, :@stimulus_classes), instance, implied, phase:, alias_map:)
|
|
168
174
|
end
|
|
169
175
|
|
|
170
|
-
def absorb_root_element_attributes(draft, instance, implied, phase:)
|
|
176
|
+
def absorb_root_element_attributes(draft, instance, implied, phase:, alias_map: {})
|
|
171
177
|
return unless instance.respond_to?(:resolved_root_element_attributes, true)
|
|
172
178
|
attrs = instance.send(:resolved_root_element_attributes)
|
|
173
179
|
return unless attrs.is_a?(Hash) && !attrs.empty?
|
|
174
180
|
|
|
175
|
-
absorb_input(draft, :controllers, attrs[:stimulus_controllers], instance, implied, phase:
|
|
176
|
-
absorb_input(draft, :actions,
|
|
177
|
-
absorb_input(draft, :targets,
|
|
178
|
-
absorb_input(draft, :outlets,
|
|
179
|
-
absorb_input(draft, :values,
|
|
180
|
-
absorb_input(draft, :params,
|
|
181
|
-
absorb_input(draft, :class_maps,
|
|
181
|
+
absorb_input(draft, :controllers, attrs[:stimulus_controllers], instance, implied, phase:, alias_map:)
|
|
182
|
+
absorb_input(draft, :actions, attrs[:stimulus_actions], instance, implied, phase:, alias_map:)
|
|
183
|
+
absorb_input(draft, :targets, attrs[:stimulus_targets], instance, implied, phase:, alias_map:)
|
|
184
|
+
absorb_input(draft, :outlets, attrs[:stimulus_outlets], instance, implied, phase:, alias_map:)
|
|
185
|
+
absorb_input(draft, :values, attrs[:stimulus_values], instance, implied, phase:, alias_map:)
|
|
186
|
+
absorb_input(draft, :params, attrs[:stimulus_params], instance, implied, phase:, alias_map:)
|
|
187
|
+
absorb_input(draft, :class_maps, attrs[:stimulus_classes], instance, implied, phase:, alias_map:)
|
|
182
188
|
end
|
|
183
189
|
|
|
184
|
-
|
|
185
|
-
# Each Hash value / Array element may be a Proc; phase-gated.
|
|
186
|
-
def absorb_input(draft, kind, input, instance, implied, phase:)
|
|
190
|
+
def absorb_input(draft, kind, input, instance, implied, phase:, alias_map: {})
|
|
187
191
|
return if input.nil?
|
|
188
192
|
|
|
189
193
|
kind_meta = Registry.fetch(kind)
|
|
190
194
|
case input
|
|
191
|
-
|
|
192
|
-
|
|
195
|
+
in Hash => h
|
|
196
|
+
h.each do |key, raw|
|
|
193
197
|
is_proc = raw.is_a?(Proc)
|
|
194
198
|
next unless phase_allows?(is_proc, phase)
|
|
195
199
|
absorbed = is_proc ? instance.instance_exec(&raw) : raw
|
|
196
200
|
next if absorbed.nil?
|
|
197
|
-
if kind_meta.keyed
|
|
201
|
+
if kind_meta.keyed?
|
|
198
202
|
parsed = kind_meta.value_class.parse(key, absorbed, implied: implied, component_id: instance_id(instance))
|
|
199
203
|
draft.public_send(:"add_#{kind}", parsed)
|
|
200
204
|
else
|
|
201
|
-
|
|
205
|
+
entry = resolve_absorb_alias(kind, [key, absorbed], alias_map)
|
|
206
|
+
parsed = parse_entry(kind_meta, entry, implied: implied, component_id: instance_id(instance))
|
|
202
207
|
draft.public_send(:"add_#{kind}", parsed) if parsed
|
|
203
208
|
end
|
|
204
209
|
end
|
|
205
|
-
|
|
206
|
-
|
|
210
|
+
in Array => a
|
|
211
|
+
a.each do |entry|
|
|
207
212
|
is_proc = entry.is_a?(Proc)
|
|
208
213
|
next unless phase_allows?(is_proc, phase)
|
|
209
|
-
parsed = absorb_one(kind_meta, entry, instance, implied)
|
|
214
|
+
parsed = absorb_one(kind_meta, entry, instance, implied, kind:, alias_map:)
|
|
210
215
|
draft.public_send(:"add_#{kind}", parsed) if parsed
|
|
211
216
|
end
|
|
212
217
|
else
|
|
213
218
|
is_proc = input.is_a?(Proc)
|
|
214
219
|
return unless phase_allows?(is_proc, phase)
|
|
215
|
-
parsed = absorb_one(kind_meta, input, instance, implied)
|
|
220
|
+
parsed = absorb_one(kind_meta, input, instance, implied, kind:, alias_map:)
|
|
216
221
|
draft.public_send(:"add_#{kind}", parsed) if parsed
|
|
217
222
|
end
|
|
218
223
|
end
|
|
219
224
|
|
|
220
|
-
|
|
225
|
+
def resolve_absorb_alias(kind, entry, alias_map)
|
|
226
|
+
return entry unless kind == :actions && alias_map.any?
|
|
227
|
+
return entry unless entry.is_a?(Hash) && entry[:controller].is_a?(Symbol)
|
|
228
|
+
sym = entry[:controller]
|
|
229
|
+
unless alias_map.key?(sym)
|
|
230
|
+
raise ::Vident::DeclarationError, "Unknown controller alias :#{sym} in stimulus_actions input. Declared aliases: #{alias_map.keys.inspect}"
|
|
231
|
+
end
|
|
232
|
+
entry.merge(controller: alias_map[sym])
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# Uses pattern matching so an unknown phase raises NoMatchingPatternError early.
|
|
221
236
|
def phase_allows?(is_proc, phase)
|
|
222
237
|
case phase
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
238
|
+
in :all then true
|
|
239
|
+
in :static then !is_proc
|
|
240
|
+
in :procs then is_proc
|
|
226
241
|
end
|
|
227
242
|
end
|
|
228
243
|
|
|
229
|
-
def absorb_one(kind_meta, entry, instance, implied)
|
|
244
|
+
def absorb_one(kind_meta, entry, instance, implied, kind: nil, alias_map: {})
|
|
230
245
|
entry = instance.instance_exec(&entry) if entry.is_a?(Proc)
|
|
231
246
|
return nil if entry.nil?
|
|
232
247
|
return entry if entry.is_a?(kind_meta.value_class)
|
|
233
248
|
|
|
249
|
+
entry = resolve_absorb_alias(kind, entry, alias_map) if kind
|
|
234
250
|
parse_entry(kind_meta, entry, implied: implied, component_id: instance_id(instance))
|
|
235
251
|
end
|
|
236
252
|
|
|
237
253
|
def parse_entry(kind_meta, entry, implied:, component_id:)
|
|
238
254
|
case entry
|
|
239
255
|
when Hash
|
|
240
|
-
if kind_meta.keyed
|
|
256
|
+
if kind_meta.keyed?
|
|
241
257
|
first_key, first_val = entry.first
|
|
242
258
|
kind_meta.value_class.parse(first_key, first_val, implied: implied, component_id: component_id)
|
|
243
259
|
else
|
|
@@ -250,15 +266,13 @@ module Vident2
|
|
|
250
266
|
end
|
|
251
267
|
end
|
|
252
268
|
|
|
253
|
-
#
|
|
254
|
-
# false / blank strings / empty collections reach the parser.
|
|
269
|
+
# Only nil drops — false, blank strings, and empty collections reach the parser.
|
|
255
270
|
def resolve_args(args, instance)
|
|
256
271
|
resolved = args.map { |arg| arg.is_a?(Proc) ? instance.instance_exec(&arg) : arg }
|
|
257
272
|
return nil if resolved.any?(&:nil?)
|
|
258
273
|
resolved
|
|
259
274
|
end
|
|
260
275
|
|
|
261
|
-
# Values accept the raw in args (proc or literal) or meta (`static:`).
|
|
262
276
|
def resolve_value_meta(decl, instance)
|
|
263
277
|
return decl.meta[:static] if decl.meta.key?(:static)
|
|
264
278
|
return nil if decl.args.empty?
|
|
@@ -279,7 +293,6 @@ module Vident2
|
|
|
279
293
|
|
|
280
294
|
def key_for_parse(key) = key
|
|
281
295
|
|
|
282
|
-
# Controller parse takes `as:` as a kwarg, not a positional.
|
|
283
296
|
def meta_for_controller(meta) = meta.slice(:as)
|
|
284
297
|
|
|
285
298
|
def instance_ivar(instance, name)
|
|
@@ -287,13 +300,10 @@ module Vident2
|
|
|
287
300
|
instance.instance_variable_get(name)
|
|
288
301
|
end
|
|
289
302
|
|
|
290
|
-
#
|
|
291
|
-
#
|
|
292
|
-
# user explicitly set one.
|
|
303
|
+
# Must match the mutation-API path (#id, memoised) so DSL outlet
|
|
304
|
+
# auto-selectors and runtime-added outlets scope identically.
|
|
293
305
|
def instance_id(instance)
|
|
294
|
-
|
|
295
|
-
raw = instance.instance_variable_get(:@id)
|
|
296
|
-
raw.presence
|
|
306
|
+
instance.id
|
|
297
307
|
end
|
|
298
308
|
|
|
299
309
|
def read_prop(instance, name)
|
|
@@ -2,14 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "declaration"
|
|
4
4
|
|
|
5
|
-
module
|
|
5
|
+
module Vident
|
|
6
6
|
module Internals
|
|
7
|
-
# @api private
|
|
8
|
-
# Fluent chain returned by `target(...)` inside a `stimulus do` block.
|
|
9
|
-
# The only current chain method is `.when` (conditional inclusion);
|
|
10
|
-
# the target itself has no other DSL-facing knobs.
|
|
11
|
-
#
|
|
12
|
-
# target(:row).when { @rows.any? }
|
|
13
7
|
class TargetBuilder
|
|
14
8
|
def initialize(*args)
|
|
15
9
|
@args = args
|
data/lib/vident/stable_id.rb
CHANGED
|
@@ -5,9 +5,9 @@ require "digest/md5"
|
|
|
5
5
|
|
|
6
6
|
module Vident
|
|
7
7
|
class StableId
|
|
8
|
-
class GeneratorNotSetError <
|
|
8
|
+
class GeneratorNotSetError < ::Vident::ConfigurationError; end
|
|
9
9
|
|
|
10
|
-
class StrategyNotConfiguredError <
|
|
10
|
+
class StrategyNotConfiguredError < ::Vident::ConfigurationError; end
|
|
11
11
|
|
|
12
12
|
RANDOM_FALLBACK = ->(generator) do
|
|
13
13
|
return Random.hex(16) unless generator
|
|
@@ -25,7 +25,7 @@ module Vident
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
class << self
|
|
28
|
-
# Callable(generator_or_nil) -> String.
|
|
28
|
+
# Callable(generator_or_nil) -> String. Host app must configure before first render.
|
|
29
29
|
attr_accessor :strategy
|
|
30
30
|
|
|
31
31
|
def set_current_sequence_generator(seed:)
|
|
@@ -3,18 +3,14 @@
|
|
|
3
3
|
require "literal"
|
|
4
4
|
require_relative "naming"
|
|
5
5
|
require_relative "controller"
|
|
6
|
+
require_relative "base"
|
|
6
7
|
|
|
7
|
-
module
|
|
8
|
+
module Vident
|
|
8
9
|
module Stimulus
|
|
9
10
|
# `data-action` fragment: single action descriptor like
|
|
10
11
|
# `"click->admin--users#handleClick"`.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# separate Descriptor class in V2; Hash DSL input parses directly into
|
|
14
|
-
# an `Action`.
|
|
15
|
-
class Action < ::Literal::Data
|
|
16
|
-
# Stimulus action options (`:once`, `:prevent`, etc.). Keep in sync
|
|
17
|
-
# with https://stimulus.hotwired.dev/reference/actions#options.
|
|
12
|
+
class Action < Base
|
|
13
|
+
# Keep in sync with https://stimulus.hotwired.dev/reference/actions#options.
|
|
18
14
|
VALID_OPTIONS = %i[once prevent stop passive !passive capture self].freeze
|
|
19
15
|
|
|
20
16
|
prop :controller, Controller
|
|
@@ -24,15 +20,12 @@ module Vident2
|
|
|
24
20
|
prop :keyboard, _Nilable(String), default: nil
|
|
25
21
|
prop :window, _Boolean, default: false
|
|
26
22
|
|
|
27
|
-
# `.parse(*args, implied:)` grammar mirrors v1 `StimulusAction#parse_arguments`:
|
|
28
|
-
# (Symbol) -> :method on implied controller, no event
|
|
29
|
-
# (String) -> pre-qualified "event->ctrl#method" / "ctrl#method"
|
|
30
|
-
# (Hash) -> keyword descriptor (method:/event:/...)
|
|
31
|
-
# (Symbol, Symbol) -> (event, method) on implied
|
|
32
|
-
# (String, Symbol) -> (controller_path, method) — no event
|
|
33
|
-
# (Symbol, String, Symbol) -> (event, controller_path, method)
|
|
34
23
|
def self.parse(*args, implied:, component_id: nil)
|
|
35
24
|
case args
|
|
25
|
+
in [Action => a]
|
|
26
|
+
a
|
|
27
|
+
in [Symbol => event, Action => a]
|
|
28
|
+
a.with(event: event.to_s)
|
|
36
29
|
in [Hash => h]
|
|
37
30
|
from_descriptor(h, implied: implied)
|
|
38
31
|
in [Symbol => method_sym]
|
|
@@ -62,11 +55,10 @@ module Vident2
|
|
|
62
55
|
event: event.to_s
|
|
63
56
|
)
|
|
64
57
|
else
|
|
65
|
-
raise ::
|
|
58
|
+
raise ::Vident::ParseError, "Action.parse: invalid arguments #{args.inspect}"
|
|
66
59
|
end
|
|
67
60
|
end
|
|
68
61
|
|
|
69
|
-
# Serialised descriptor, e.g. `"click.esc:prevent@window->foo--bar#handle"`.
|
|
70
62
|
def to_s
|
|
71
63
|
head =
|
|
72
64
|
if event
|
|
@@ -86,18 +78,15 @@ module Vident2
|
|
|
86
78
|
def to_h = {action: to_s}
|
|
87
79
|
alias_method :to_hash, :to_h
|
|
88
80
|
|
|
89
|
-
# Actions space-join under a single `:action` key, preserving order.
|
|
90
81
|
def self.to_data_hash(actions)
|
|
91
82
|
return {} if actions.empty?
|
|
92
83
|
{action: actions.map(&:to_s).join(" ")}
|
|
93
84
|
end
|
|
94
85
|
|
|
95
|
-
# `.parse({event:, method:, controller:, options:, keyboard:, window:})`
|
|
96
|
-
# Keyword-descriptor entry point, used by the DSL Hash form.
|
|
97
86
|
def self.from_descriptor(h, implied:)
|
|
98
87
|
invalid_options = Array(h[:options]) - VALID_OPTIONS
|
|
99
88
|
unless invalid_options.empty?
|
|
100
|
-
raise ::
|
|
89
|
+
raise ::Vident::ParseError,
|
|
101
90
|
"Action.parse: invalid option(s) #{invalid_options.inspect}. Valid: #{VALID_OPTIONS.inspect}"
|
|
102
91
|
end
|
|
103
92
|
|
|
@@ -114,9 +103,7 @@ module Vident2
|
|
|
114
103
|
)
|
|
115
104
|
end
|
|
116
105
|
|
|
117
|
-
#
|
|
118
|
-
# `"admin--users#show"`. Pass-through: the controller segment is NOT
|
|
119
|
-
# re-stimulized. Flagged for deprecation.
|
|
106
|
+
# Pass-through: the controller segment is NOT re-stimulized.
|
|
120
107
|
def self.parse_qualified_string(s)
|
|
121
108
|
if s.include?("->")
|
|
122
109
|
event_part, ctrl_method = s.split("->", 2)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "literal"
|
|
4
|
+
require_relative "combinable"
|
|
5
|
+
|
|
6
|
+
module Vident
|
|
7
|
+
module Stimulus
|
|
8
|
+
# Shared frozen-value base for the Stimulus primitive value classes
|
|
9
|
+
# (Action, Target, Controller, Outlet, Value, Param, ClassMap).
|
|
10
|
+
# Provides `Combinable` (`with`, pattern-matching `deconstruct_keys`)
|
|
11
|
+
# and a default `to_data_hash(items)` that subclasses override when
|
|
12
|
+
# they need non-trivial collection semantics (space-join etc.).
|
|
13
|
+
#
|
|
14
|
+
# Subclasses still override `to_h` / `to_hash` per class — Literal
|
|
15
|
+
# auto-generates a prop-hash `to_h` from the prop DSL that would
|
|
16
|
+
# shadow any default here, so the data-attribute-shape override
|
|
17
|
+
# must live on each concrete class.
|
|
18
|
+
class Base < ::Literal::Data
|
|
19
|
+
include Combinable
|
|
20
|
+
|
|
21
|
+
def self.to_data_hash(items)
|
|
22
|
+
items.to_h(&:to_data_pair)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -2,19 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
require "literal"
|
|
4
4
|
require_relative "naming"
|
|
5
|
+
require_relative "base"
|
|
5
6
|
require_relative "controller"
|
|
6
7
|
|
|
7
|
-
module
|
|
8
|
+
module Vident
|
|
8
9
|
module Stimulus
|
|
9
10
|
# `data-<ctrl>-<name>-class` fragment — a named CSS class set readable
|
|
10
|
-
# on the JS side as `this.<name>Class`.
|
|
11
|
-
|
|
12
|
-
# to type in user code).
|
|
13
|
-
#
|
|
14
|
-
# `css` holds the final serialised string form (space-joined); the
|
|
15
|
-
# parser normalises String / Array-of-String / Array-of-anything inputs
|
|
16
|
-
# down to one shape so the Draft/Plan doesn't have to care.
|
|
17
|
-
class ClassMap < ::Literal::Data
|
|
11
|
+
# on the JS side as `this.<name>Class`.
|
|
12
|
+
class ClassMap < Base
|
|
18
13
|
prop :controller, Controller
|
|
19
14
|
prop :name, String
|
|
20
15
|
prop :css, String
|
|
@@ -34,7 +29,7 @@ module Vident2
|
|
|
34
29
|
css: normalize_css(css_input)
|
|
35
30
|
)
|
|
36
31
|
else
|
|
37
|
-
raise ::
|
|
32
|
+
raise ::Vident::ParseError, "ClassMap.parse: invalid arguments #{args.inspect}"
|
|
38
33
|
end
|
|
39
34
|
end
|
|
40
35
|
|
|
@@ -45,7 +40,7 @@ module Vident2
|
|
|
45
40
|
when Array
|
|
46
41
|
input.map(&:to_s).reject(&:empty?).join(" ")
|
|
47
42
|
else
|
|
48
|
-
raise ::
|
|
43
|
+
raise ::Vident::ParseError, "ClassMap.parse: css must be a String or Array, got #{input.class}"
|
|
49
44
|
end
|
|
50
45
|
end
|
|
51
46
|
|
|
@@ -57,13 +52,6 @@ module Vident2
|
|
|
57
52
|
|
|
58
53
|
def to_h = {data_attribute_key => css}
|
|
59
54
|
alias_method :to_hash, :to_h
|
|
60
|
-
|
|
61
|
-
def self.to_data_hash(maps)
|
|
62
|
-
maps.each_with_object({}) do |m, acc|
|
|
63
|
-
key, str = m.to_data_pair
|
|
64
|
-
acc[key] = str
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
55
|
end
|
|
68
56
|
end
|
|
69
57
|
end
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
module
|
|
3
|
+
module Vident
|
|
4
4
|
module Stimulus
|
|
5
|
-
# Tiny aggregation wrapper for the plural `stimulus_<kind>s` parsers.
|
|
6
|
-
# Parametric over `kind`: the kind decides the per-element combining
|
|
7
|
-
# rule via its value class's `.to_data_hash`. Users interact with
|
|
8
|
-
# this object by splatting `{**component.stimulus_targets(...)}` into
|
|
9
|
-
# a `data:` option on a tag, so `#to_h` is the single required shape.
|
|
10
5
|
class Collection
|
|
11
6
|
include Enumerable
|
|
12
7
|
|
|
@@ -18,14 +13,17 @@ module Vident2
|
|
|
18
13
|
end
|
|
19
14
|
|
|
20
15
|
def each(&block) = @items.each(&block)
|
|
16
|
+
|
|
21
17
|
def to_a = @items.dup
|
|
18
|
+
|
|
22
19
|
def size = @items.size
|
|
20
|
+
|
|
23
21
|
def length = @items.size
|
|
22
|
+
|
|
24
23
|
def empty? = @items.empty?
|
|
24
|
+
|
|
25
25
|
def any? = @items.any?
|
|
26
26
|
|
|
27
|
-
# Delegates to the kind's `.to_data_hash` — same path AttributeWriter
|
|
28
|
-
# uses at render time.
|
|
29
27
|
def to_h
|
|
30
28
|
@kind.value_class.to_data_hash(@items)
|
|
31
29
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Vident
|
|
4
|
+
module Stimulus
|
|
5
|
+
# Shared `with(**overrides)` combinator for the frozen value classes.
|
|
6
|
+
# Mirrors Ruby's `Data.define#with` convention (which Literal::Data
|
|
7
|
+
# doesn't ship) so callers can decorate a value object without
|
|
8
|
+
# mutating it.
|
|
9
|
+
module Combinable
|
|
10
|
+
# Canonical Ruby Data-object hooks. The value classes override `to_h`
|
|
11
|
+
# to serialise to their data-attribute shape; without this module,
|
|
12
|
+
# `deconstruct_keys` would inherit that override and pattern-matching
|
|
13
|
+
# (`case a; in {event:}`) would silently fail.
|
|
14
|
+
def deconstruct_keys(keys)
|
|
15
|
+
h = self.class.literal_properties.properties_index.each_with_object({}) do |(name, _), acc|
|
|
16
|
+
acc[name.to_sym] = public_send(name)
|
|
17
|
+
end
|
|
18
|
+
keys ? h.slice(*keys) : h
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def deconstruct
|
|
22
|
+
deconstruct_keys(nil).values
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def with(**overrides)
|
|
26
|
+
self.class.new(**deconstruct_keys(nil).merge(overrides))
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|