view_component 2.83.0 → 3.21.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/app/controllers/concerns/view_component/preview_actions.rb +5 -1
- data/app/controllers/view_components_system_test_controller.rb +24 -1
- data/app/helpers/preview_helper.rb +22 -4
- data/app/views/view_components/_preview_source.html.erb +2 -2
- data/docs/CHANGELOG.md +807 -1
- data/lib/rails/generators/abstract_generator.rb +9 -1
- data/lib/rails/generators/component/component_generator.rb +2 -1
- data/lib/rails/generators/component/templates/component.rb.tt +3 -2
- data/lib/rails/generators/erb/component_generator.rb +1 -1
- data/lib/rails/generators/locale/component_generator.rb +3 -3
- data/lib/rails/generators/preview/templates/component_preview.rb.tt +2 -0
- data/lib/rails/generators/rspec/component_generator.rb +15 -3
- data/lib/rails/generators/rspec/templates/component_spec.rb.tt +1 -1
- data/lib/rails/generators/stimulus/component_generator.rb +8 -3
- data/lib/rails/generators/stimulus/templates/component_controller.ts.tt +9 -0
- data/lib/rails/generators/test_unit/templates/component_test.rb.tt +1 -1
- data/lib/view_component/base.rb +169 -164
- data/lib/view_component/capture_compatibility.rb +44 -0
- data/lib/view_component/collection.rb +20 -8
- data/lib/view_component/compiler.rb +166 -207
- data/lib/view_component/config.rb +63 -14
- data/lib/view_component/deprecation.rb +1 -1
- data/lib/view_component/docs_builder_component.html.erb +5 -1
- data/lib/view_component/docs_builder_component.rb +28 -9
- data/lib/view_component/engine.rb +58 -28
- data/lib/view_component/errors.rb +240 -0
- data/lib/view_component/inline_template.rb +55 -0
- data/lib/view_component/instrumentation.rb +10 -2
- data/lib/view_component/preview.rb +7 -8
- data/lib/view_component/rails/tasks/view_component.rake +11 -2
- data/lib/view_component/slot.rb +119 -1
- data/lib/view_component/slotable.rb +394 -94
- data/lib/view_component/slotable_default.rb +20 -0
- data/lib/view_component/system_test_helpers.rb +5 -5
- data/lib/view_component/template.rb +134 -0
- data/lib/view_component/test_helpers.rb +138 -59
- data/lib/view_component/translatable.rb +45 -26
- data/lib/view_component/use_helpers.rb +42 -0
- data/lib/view_component/version.rb +4 -3
- data/lib/view_component/with_content_helper.rb +3 -8
- data/lib/view_component.rb +3 -12
- metadata +277 -38
- data/lib/view_component/content_areas.rb +0 -56
- data/lib/view_component/polymorphic_slots.rb +0 -103
- data/lib/view_component/preview_template_error.rb +0 -6
- data/lib/view_component/slot_v2.rb +0 -98
- data/lib/view_component/slotable_v2.rb +0 -391
- data/lib/view_component/template_error.rb +0 -9
@@ -7,7 +7,6 @@ module ViewComponent
|
|
7
7
|
include Enumerable
|
8
8
|
attr_reader :component
|
9
9
|
|
10
|
-
delegate :format, to: :component
|
11
10
|
delegate :size, to: :@collection
|
12
11
|
|
13
12
|
attr_accessor :__vc_original_view_context
|
@@ -20,7 +19,7 @@ module ViewComponent
|
|
20
19
|
components.map do |component|
|
21
20
|
component.set_original_view_context(__vc_original_view_context)
|
22
21
|
component.render_in(view_context, &block)
|
23
|
-
end.join.html_safe
|
22
|
+
end.join(rendered_spacer(view_context)).html_safe
|
24
23
|
end
|
25
24
|
|
26
25
|
def components
|
@@ -41,11 +40,18 @@ module ViewComponent
|
|
41
40
|
components.each(&block)
|
42
41
|
end
|
43
42
|
|
43
|
+
# Rails expects us to define `format` on all renderables,
|
44
|
+
# but we do not know the `format` of a ViewComponent until runtime.
|
45
|
+
def format
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
44
49
|
private
|
45
50
|
|
46
|
-
def initialize(component, object, **options)
|
51
|
+
def initialize(component, object, spacer_component, **options)
|
47
52
|
@component = component
|
48
53
|
@collection = collection_variable(object || [])
|
54
|
+
@spacer_component = spacer_component
|
49
55
|
@options = options
|
50
56
|
end
|
51
57
|
|
@@ -53,19 +59,25 @@ module ViewComponent
|
|
53
59
|
if object.respond_to?(:to_ary)
|
54
60
|
object.to_ary
|
55
61
|
else
|
56
|
-
raise
|
57
|
-
"The value of the first argument passed to `with_collection` isn't a valid collection. " \
|
58
|
-
"Make sure it responds to `to_ary`."
|
59
|
-
)
|
62
|
+
raise InvalidCollectionArgumentError
|
60
63
|
end
|
61
64
|
end
|
62
65
|
|
63
66
|
def component_options(item, iterator)
|
64
67
|
item_options = {component.collection_parameter => item}
|
65
|
-
item_options[component.collection_counter_parameter] = iterator.index
|
68
|
+
item_options[component.collection_counter_parameter] = iterator.index if component.counter_argument_present?
|
66
69
|
item_options[component.collection_iteration_parameter] = iterator.dup if component.iteration_argument_present?
|
67
70
|
|
68
71
|
@options.merge(item_options)
|
69
72
|
end
|
73
|
+
|
74
|
+
def rendered_spacer(view_context)
|
75
|
+
if @spacer_component
|
76
|
+
@spacer_component.set_original_view_context(__vc_original_view_context)
|
77
|
+
@spacer_component.render_in(view_context)
|
78
|
+
else
|
79
|
+
""
|
80
|
+
end
|
81
|
+
end
|
70
82
|
end
|
71
83
|
end
|
@@ -4,267 +4,226 @@ require "concurrent-ruby"
|
|
4
4
|
|
5
5
|
module ViewComponent
|
6
6
|
class Compiler
|
7
|
-
# Compiler mode. Can be either:
|
8
|
-
# *
|
7
|
+
# Compiler development mode. Can be either:
|
8
|
+
# * true (a blocking mode which ensures thread safety when redefining the `call` method for components,
|
9
9
|
# default in Rails development and test mode)
|
10
|
-
# *
|
11
|
-
|
12
|
-
PRODUCTION_MODE = :production
|
10
|
+
# * false(a non-blocking mode, default in Rails production mode)
|
11
|
+
class_attribute :development_mode, default: false
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
@component_class = component_class
|
18
|
-
@redefinition_lock = Mutex.new
|
19
|
-
@variants_rendering_templates = Set.new
|
13
|
+
def initialize(component)
|
14
|
+
@component = component
|
15
|
+
@lock = Mutex.new
|
20
16
|
end
|
21
17
|
|
22
18
|
def compiled?
|
23
|
-
CompileCache.compiled?(
|
24
|
-
end
|
25
|
-
|
26
|
-
def development?
|
27
|
-
self.class.mode == DEVELOPMENT_MODE
|
19
|
+
CompileCache.compiled?(@component)
|
28
20
|
end
|
29
21
|
|
30
22
|
def compile(raise_errors: false, force: false)
|
31
23
|
return if compiled? && !force
|
32
|
-
return if
|
33
|
-
|
34
|
-
component_class.superclass.compile(raise_errors: raise_errors) if should_compile_superclass?
|
35
|
-
subclass_instance_methods = component_class.instance_methods(false)
|
24
|
+
return if @component == ViewComponent::Base
|
36
25
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
)
|
42
|
-
end
|
43
|
-
|
44
|
-
if template_errors.present?
|
45
|
-
raise ViewComponent::TemplateError.new(template_errors) if raise_errors
|
26
|
+
@lock.synchronize do
|
27
|
+
# this check is duplicated so that concurrent compile calls can still
|
28
|
+
# early exit
|
29
|
+
return if compiled? && !force
|
46
30
|
|
47
|
-
|
48
|
-
end
|
31
|
+
gather_templates
|
49
32
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
)
|
54
|
-
end
|
33
|
+
if self.class.development_mode && @templates.any?(&:requires_compiled_superclass?)
|
34
|
+
@component.superclass.compile(raise_errors: raise_errors)
|
35
|
+
end
|
55
36
|
|
56
|
-
|
57
|
-
|
58
|
-
component_class.validate_collection_parameter!
|
59
|
-
end
|
37
|
+
if template_errors.present?
|
38
|
+
raise TemplateError.new(template_errors) if raise_errors
|
60
39
|
|
61
|
-
|
62
|
-
|
63
|
-
# as Ruby warns when redefining a method.
|
64
|
-
method_name = call_method_name(template[:variant])
|
65
|
-
@variants_rendering_templates << template[:variant]
|
66
|
-
|
67
|
-
redefinition_lock.synchronize do
|
68
|
-
component_class.silence_redefinition_of_method(method_name)
|
69
|
-
# rubocop:disable Style/EvalWithLocation
|
70
|
-
component_class.class_eval <<-RUBY, template[:path], 0
|
71
|
-
def #{method_name}
|
72
|
-
#{compiled_template(template[:path])}
|
73
|
-
end
|
74
|
-
RUBY
|
75
|
-
# rubocop:enable Style/EvalWithLocation
|
40
|
+
# this return is load bearing, and prevents the component from being considered "compiled?"
|
41
|
+
return false
|
76
42
|
end
|
77
|
-
end
|
78
43
|
|
79
|
-
|
44
|
+
if raise_errors
|
45
|
+
@component.validate_initialization_parameters!
|
46
|
+
@component.validate_collection_parameter!
|
47
|
+
end
|
80
48
|
|
81
|
-
|
49
|
+
define_render_template_for
|
82
50
|
|
83
|
-
|
84
|
-
|
51
|
+
@component.register_default_slots
|
52
|
+
@component.build_i18n_backend
|
85
53
|
|
86
|
-
|
87
|
-
|
54
|
+
CompileCache.register(@component)
|
55
|
+
end
|
88
56
|
end
|
89
57
|
|
90
58
|
private
|
91
59
|
|
92
|
-
attr_reader :
|
60
|
+
attr_reader :templates
|
93
61
|
|
94
62
|
def define_render_template_for
|
95
|
-
|
96
|
-
|
97
|
-
end
|
98
|
-
|
99
|
-
|
100
|
-
if
|
101
|
-
|
102
|
-
|
63
|
+
@templates.each do |template|
|
64
|
+
template.compile_to_component
|
65
|
+
end
|
66
|
+
|
67
|
+
method_body =
|
68
|
+
if @templates.one?
|
69
|
+
@templates.first.safe_method_name_call
|
70
|
+
elsif (template = @templates.find(&:inline?))
|
71
|
+
template.safe_method_name_call
|
103
72
|
else
|
104
|
-
|
105
|
-
|
106
|
-
|
73
|
+
branches = []
|
74
|
+
|
75
|
+
@templates.each do |template|
|
76
|
+
conditional =
|
77
|
+
if template.inline_call?
|
78
|
+
"variant&.to_sym == #{template.variant.inspect}"
|
79
|
+
else
|
80
|
+
[
|
81
|
+
template.default_format? ? "(format == #{ViewComponent::Base::VC_INTERNAL_DEFAULT_FORMAT.inspect} || format.nil?)" : "format == #{template.format.inspect}",
|
82
|
+
template.variant.nil? ? "variant.nil?" : "variant&.to_sym == #{template.variant.inspect}"
|
83
|
+
].join(" && ")
|
84
|
+
end
|
85
|
+
|
86
|
+
branches << [conditional, template.safe_method_name_call]
|
87
|
+
end
|
107
88
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
#{body}
|
89
|
+
out = branches.each_with_object(+"") do |(conditional, branch_body), memo|
|
90
|
+
memo << "#{(!memo.present?) ? "if" : "elsif"} #{conditional}\n #{branch_body}\n"
|
91
|
+
end
|
92
|
+
out << "else\n #{templates.find { _1.variant.nil? && _1.default_format? }.safe_method_name_call}\nend"
|
113
93
|
end
|
114
|
-
|
94
|
+
|
95
|
+
@component.silence_redefinition_of_method(:render_template_for)
|
96
|
+
@component.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
97
|
+
def render_template_for(variant = nil, format = nil)
|
98
|
+
#{method_body}
|
115
99
|
end
|
100
|
+
RUBY
|
116
101
|
end
|
117
102
|
|
118
103
|
def template_errors
|
119
|
-
@
|
120
|
-
|
121
|
-
errors = []
|
104
|
+
@_template_errors ||= begin
|
105
|
+
errors = []
|
122
106
|
|
123
|
-
|
124
|
-
errors << "Couldn't find a template file or inline render method for #{component_class}."
|
125
|
-
end
|
107
|
+
errors << "Couldn't find a template file or inline render method for #{@component}." if @templates.empty?
|
126
108
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
109
|
+
# We currently allow components to have both an inline call method and a template for a variant, with the
|
110
|
+
# inline call method overriding the template. We should aim to change this in v4 to instead
|
111
|
+
# raise an error.
|
112
|
+
@templates.reject(&:inline_call?)
|
113
|
+
.map { |template| [template.variant, template.format] }
|
114
|
+
.tally
|
115
|
+
.select { |_, count| count > 1 }
|
116
|
+
.each do |tally|
|
117
|
+
variant, this_format = tally.first
|
132
118
|
|
133
|
-
|
134
|
-
templates
|
135
|
-
.group_by { |template| template[:variant] }
|
136
|
-
.map { |variant, grouped| variant if grouped.length > 1 }
|
137
|
-
.compact
|
138
|
-
.sort
|
139
|
-
|
140
|
-
unless invalid_variants.empty?
|
141
|
-
errors <<
|
142
|
-
"More than one template found for #{"variant".pluralize(invalid_variants.count)} " \
|
143
|
-
"#{invalid_variants.map { |v| "'#{v}'" }.to_sentence} in #{component_class}. " \
|
144
|
-
"There can only be one template file per variant."
|
145
|
-
end
|
119
|
+
variant_string = " for variant `#{variant}`" if variant.present?
|
146
120
|
|
147
|
-
|
148
|
-
|
149
|
-
"Template file and inline render method found for #{component_class}. " \
|
150
|
-
"There can only be a template file or inline render method per component."
|
151
|
-
end
|
121
|
+
errors << "More than one #{this_format.upcase} template found#{variant_string} for #{@component}. "
|
122
|
+
end
|
152
123
|
|
153
|
-
|
154
|
-
|
124
|
+
default_template_types = @templates.each_with_object(Set.new) do |template, memo|
|
125
|
+
next if template.variant
|
155
126
|
|
156
|
-
|
157
|
-
|
127
|
+
memo << :template_file if !template.inline_call?
|
128
|
+
memo << :inline_render if template.inline_call? && template.defined_on_self?
|
158
129
|
|
159
|
-
|
160
|
-
|
161
|
-
"found for #{"variant".pluralize(count)} " \
|
162
|
-
"#{duplicate_template_file_and_inline_variant_calls.map { |v| "'#{v}'" }.to_sentence} " \
|
163
|
-
"in #{component_class}. " \
|
164
|
-
"There can only be a template file or inline render method per variant."
|
165
|
-
end
|
130
|
+
memo
|
131
|
+
end
|
166
132
|
|
167
|
-
|
168
|
-
|
133
|
+
if default_template_types.length > 1
|
134
|
+
errors <<
|
135
|
+
"Template file and inline render method found for #{@component}. " \
|
136
|
+
"There can only be a template file or inline render method per component."
|
137
|
+
end
|
169
138
|
|
170
|
-
|
171
|
-
|
172
|
-
|
139
|
+
# If a template has inline calls, they can conflict with template files the component may use
|
140
|
+
# to render. This attempts to catch and raise that issue before run time. For example,
|
141
|
+
# `def render_mobile` would conflict with a sidecar template of `component.html+mobile.erb`
|
142
|
+
duplicate_template_file_and_inline_call_variants =
|
143
|
+
@templates.reject(&:inline_call?).map(&:variant) &
|
144
|
+
@templates.select { _1.inline_call? && _1.defined_on_self? }.map(&:variant)
|
145
|
+
|
146
|
+
unless duplicate_template_file_and_inline_call_variants.empty?
|
147
|
+
count = duplicate_template_file_and_inline_call_variants.count
|
148
|
+
|
149
|
+
errors <<
|
150
|
+
"Template #{"file".pluralize(count)} and inline render #{"method".pluralize(count)} " \
|
151
|
+
"found for #{"variant".pluralize(count)} " \
|
152
|
+
"#{duplicate_template_file_and_inline_call_variants.map { |v| "'#{v}'" }.to_sentence} " \
|
153
|
+
"in #{@component}. There can only be a template file or inline render method per variant."
|
154
|
+
end
|
173
155
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
156
|
+
@templates.select(&:variant).each_with_object(Hash.new { |h, k| h[k] = Set.new }) do |template, memo|
|
157
|
+
memo[template.normalized_variant_name] << template.variant
|
158
|
+
memo
|
159
|
+
end.each do |_, variant_names|
|
160
|
+
next unless variant_names.length > 1
|
179
161
|
|
180
|
-
errors
|
162
|
+
errors << "Colliding templates #{variant_names.sort.map { |v| "'#{v}'" }.to_sentence} found in #{@component}."
|
181
163
|
end
|
164
|
+
|
165
|
+
errors
|
166
|
+
end
|
182
167
|
end
|
183
168
|
|
184
|
-
def
|
169
|
+
def gather_templates
|
185
170
|
@templates ||=
|
186
171
|
begin
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
172
|
+
templates = @component.sidecar_files(
|
173
|
+
ActionView::Template.template_handler_extensions
|
174
|
+
).map do |path|
|
175
|
+
# Extract format and variant from template filename
|
176
|
+
this_format, variant =
|
177
|
+
File
|
178
|
+
.basename(path) # "variants_component.html+mini.watch.erb"
|
179
|
+
.split(".")[1..-2] # ["html+mini", "watch"]
|
180
|
+
.join(".") # "html+mini.watch"
|
181
|
+
.split("+") # ["html", "mini.watch"]
|
182
|
+
.map(&:to_sym) # [:html, :"mini.watch"]
|
183
|
+
|
184
|
+
out = Template.new(
|
185
|
+
component: @component,
|
186
|
+
type: :file,
|
192
187
|
path: path,
|
193
|
-
|
194
|
-
|
195
|
-
|
188
|
+
lineno: 0,
|
189
|
+
extension: path.split(".").last,
|
190
|
+
this_format: this_format.to_s.split(".").last&.to_sym, # strip locale from this_format, see #2113
|
191
|
+
variant: variant
|
192
|
+
)
|
193
|
+
|
194
|
+
out
|
196
195
|
end
|
197
|
-
end
|
198
|
-
end
|
199
196
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
197
|
+
component_instance_methods_on_self = @component.instance_methods(false)
|
198
|
+
|
199
|
+
(
|
200
|
+
@component.ancestors.take_while { |ancestor| ancestor != ViewComponent::Base } - @component.included_modules
|
201
|
+
).flat_map { |ancestor| ancestor.instance_methods(false).grep(/^call(_|$)/) }
|
202
|
+
.uniq
|
203
|
+
.each do |method_name|
|
204
|
+
templates << Template.new(
|
205
|
+
component: @component,
|
206
|
+
type: :inline_call,
|
207
|
+
this_format: ViewComponent::Base::VC_INTERNAL_DEFAULT_FORMAT,
|
208
|
+
variant: method_name.to_s.include?("call_") ? method_name.to_s.sub("call_", "").to_sym : nil,
|
209
|
+
method_name: method_name,
|
210
|
+
defined_on_self: component_instance_methods_on_self.include?(method_name)
|
211
|
+
)
|
212
|
+
end
|
213
|
+
|
214
|
+
if @component.inline_template.present?
|
215
|
+
templates << Template.new(
|
216
|
+
component: @component,
|
217
|
+
type: :inline,
|
218
|
+
path: @component.inline_template.path,
|
219
|
+
lineno: @component.inline_template.lineno,
|
220
|
+
source: @component.inline_template.source.dup,
|
221
|
+
extension: @component.inline_template.language
|
209
222
|
)
|
223
|
+
end
|
210
224
|
|
211
|
-
|
225
|
+
templates
|
212
226
|
end
|
213
227
|
end
|
214
|
-
|
215
|
-
def inline_calls_defined_on_self
|
216
|
-
@inline_calls_defined_on_self ||= component_class.instance_methods(false).grep(/^call/)
|
217
|
-
end
|
218
|
-
|
219
|
-
def variants
|
220
|
-
@__vc_variants = (
|
221
|
-
templates.map { |template| template[:variant] } + variants_from_inline_calls(inline_calls)
|
222
|
-
).compact.uniq
|
223
|
-
end
|
224
|
-
|
225
|
-
def variants_from_inline_calls(calls)
|
226
|
-
calls.reject { |call| call == :call }.map do |variant_call|
|
227
|
-
variant_call.to_s.sub("call_", "").to_sym
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
def compiled_template(file_path)
|
232
|
-
handler = ActionView::Template.handler_for_extension(File.extname(file_path).delete("."))
|
233
|
-
template = File.read(file_path)
|
234
|
-
template.rstrip! if component_class.strip_trailing_whitespace?
|
235
|
-
|
236
|
-
if handler.method(:call).parameters.length > 1
|
237
|
-
handler.call(component_class, template)
|
238
|
-
else
|
239
|
-
handler.call(
|
240
|
-
OpenStruct.new(
|
241
|
-
source: template,
|
242
|
-
identifier: component_class.identifier,
|
243
|
-
type: component_class.type
|
244
|
-
)
|
245
|
-
)
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
def call_method_name(variant)
|
250
|
-
if variant.present? && variants.include?(variant)
|
251
|
-
"call_#{normalized_variant_name(variant)}"
|
252
|
-
else
|
253
|
-
"call"
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
|
-
def normalized_variant_name(variant)
|
258
|
-
variant.to_s.gsub("-", "__").gsub(".", "___")
|
259
|
-
end
|
260
|
-
|
261
|
-
def should_compile_superclass?
|
262
|
-
development? &&
|
263
|
-
templates.empty? &&
|
264
|
-
!(
|
265
|
-
component_class.instance_methods(false).include?(:call) ||
|
266
|
-
component_class.private_instance_methods(false).include?(:call)
|
267
|
-
)
|
268
|
-
end
|
269
228
|
end
|
270
229
|
end
|
@@ -17,13 +17,15 @@ module ViewComponent
|
|
17
17
|
preview_route: "/rails/view_components",
|
18
18
|
show_previews_source: false,
|
19
19
|
instrumentation_enabled: false,
|
20
|
+
use_deprecated_instrumentation_name: true,
|
20
21
|
render_monkey_patch_enabled: true,
|
21
22
|
view_component_path: "app/components",
|
22
23
|
component_parent_class: nil,
|
23
24
|
show_previews: Rails.env.development? || Rails.env.test?,
|
24
25
|
preview_paths: default_preview_paths,
|
25
26
|
test_controller: "ApplicationController",
|
26
|
-
default_preview_layout: nil
|
27
|
+
default_preview_layout: nil,
|
28
|
+
capture_compatibility_patch_enabled: false
|
27
29
|
})
|
28
30
|
end
|
29
31
|
|
@@ -46,6 +48,12 @@ module ViewComponent
|
|
46
48
|
#
|
47
49
|
# config.view_component.generate.stimulus_controller = true
|
48
50
|
#
|
51
|
+
# #### `#typescript`
|
52
|
+
#
|
53
|
+
# Generate TypeScript files instead of JavaScript files:
|
54
|
+
#
|
55
|
+
# config.view_component.generate.typescript = true
|
56
|
+
#
|
49
57
|
# #### `#locale`
|
50
58
|
#
|
51
59
|
# Always generate translations file alongside the component:
|
@@ -77,6 +85,19 @@ module ViewComponent
|
|
77
85
|
# Defaults to `""`. If this is blank, the generator will use
|
78
86
|
# `ViewComponent.config.preview_paths` if defined,
|
79
87
|
# `"test/components/previews"` otherwise
|
88
|
+
#
|
89
|
+
# #### `#use_component_path_for_rspec_tests`
|
90
|
+
#
|
91
|
+
# Whether to use the `config.view_component_path` when generating new
|
92
|
+
# RSpec component tests:
|
93
|
+
#
|
94
|
+
# config.view_component.generate.use_component_path_for_rspec_tests = true
|
95
|
+
#
|
96
|
+
# When set to `true`, the generator will use the `view_component_path` to
|
97
|
+
# decide where to generate the new RSpec component test.
|
98
|
+
# For example, if the `view_component_path` is
|
99
|
+
# `app/views/components`, then the generator will create a new spec file
|
100
|
+
# in `spec/views/components/` rather than the default `spec/components/`.
|
80
101
|
|
81
102
|
# @!attribute preview_controller
|
82
103
|
# @return [String]
|
@@ -98,6 +119,13 @@ module ViewComponent
|
|
98
119
|
# Whether ActiveSupport notifications are enabled.
|
99
120
|
# Defaults to `false`.
|
100
121
|
|
122
|
+
# @!attribute use_deprecated_instrumentation_name
|
123
|
+
# @return [Boolean]
|
124
|
+
# Whether ActiveSupport Notifications use the private name `"!render.view_component"`
|
125
|
+
# or are made more publicly available via `"render.view_component"`.
|
126
|
+
# Will default to `false` in next major version.
|
127
|
+
# Defaults to `true`.
|
128
|
+
|
101
129
|
# @!attribute render_monkey_patch_enabled
|
102
130
|
# @return [Boolean] Whether the #render method should be monkey patched.
|
103
131
|
# If this is disabled, use `#render_component` or
|
@@ -124,10 +152,7 @@ module ViewComponent
|
|
124
152
|
# @!attribute preview_paths
|
125
153
|
# @return [Array<String>]
|
126
154
|
# The locations in which component previews will be looked up.
|
127
|
-
# Defaults to `['test/
|
128
|
-
|
129
|
-
# @!attribute preview_path
|
130
|
-
# @deprecated Use #preview_paths instead. Will be removed in v3.0.0.
|
155
|
+
# Defaults to `['test/components/previews']` relative to your Rails root.
|
131
156
|
|
132
157
|
# @!attribute test_controller
|
133
158
|
# @return [String]
|
@@ -141,12 +166,37 @@ module ViewComponent
|
|
141
166
|
# previews.
|
142
167
|
# Defaults to `nil`. If this is falsy, `"component_preview"` is used.
|
143
168
|
|
169
|
+
# @!attribute capture_compatibility_patch_enabled
|
170
|
+
# @return [Boolean]
|
171
|
+
# Enables the experimental capture compatibility patch that makes ViewComponent
|
172
|
+
# compatible with forms, capture, and other built-ins.
|
173
|
+
# previews.
|
174
|
+
# Defaults to `false`.
|
175
|
+
|
144
176
|
def default_preview_paths
|
177
|
+
(default_rails_preview_paths + default_rails_engines_preview_paths).uniq
|
178
|
+
end
|
179
|
+
|
180
|
+
def default_rails_preview_paths
|
145
181
|
return [] unless defined?(Rails.root) && Dir.exist?("#{Rails.root}/test/components/previews")
|
146
182
|
|
147
183
|
["#{Rails.root}/test/components/previews"]
|
148
184
|
end
|
149
185
|
|
186
|
+
def default_rails_engines_preview_paths
|
187
|
+
return [] unless defined?(Rails::Engine)
|
188
|
+
|
189
|
+
registered_rails_engines_with_previews.map do |descendant|
|
190
|
+
"#{descendant.root}/test/components/previews"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def registered_rails_engines_with_previews
|
195
|
+
Rails::Engine.descendants.select do |descendant|
|
196
|
+
defined?(descendant.root) && Dir.exist?("#{descendant.root}/test/components/previews")
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
150
200
|
def default_generate_options
|
151
201
|
options = ActiveSupport::OrderedOptions.new(false)
|
152
202
|
options.preview_path = ""
|
@@ -154,19 +204,18 @@ module ViewComponent
|
|
154
204
|
end
|
155
205
|
end
|
156
206
|
|
207
|
+
# @!attribute current
|
208
|
+
# @return [ViewComponent::Config]
|
209
|
+
# Returns the current ViewComponent::Config. This is persisted against this
|
210
|
+
# class so that config options remain accessible before the rest of
|
211
|
+
# ViewComponent has loaded. Defaults to an instance of ViewComponent::Config
|
212
|
+
# with all other documented defaults set.
|
213
|
+
class_attribute :current, default: defaults, instance_predicate: false
|
214
|
+
|
157
215
|
def initialize
|
158
216
|
@config = self.class.defaults
|
159
217
|
end
|
160
218
|
|
161
|
-
def preview_path
|
162
|
-
preview_paths
|
163
|
-
end
|
164
|
-
|
165
|
-
def preview_path=(new_value)
|
166
|
-
ViewComponent::Deprecation.deprecation_warning("`preview_path`", :"`preview_paths`")
|
167
|
-
self.preview_paths = Array.wrap(new_value)
|
168
|
-
end
|
169
|
-
|
170
219
|
delegate_missing_to :config
|
171
220
|
|
172
221
|
private
|
@@ -12,7 +12,11 @@ nav_order: 3
|
|
12
12
|
## <%= section.heading %>
|
13
13
|
|
14
14
|
<% section.methods.each do |method| %>
|
15
|
-
### <%== render ViewComponent::DocsBuilderComponent::MethodDoc.new(method) %>
|
15
|
+
### <%== render ViewComponent::DocsBuilderComponent::MethodDoc.new(method, section.show_types) %>
|
16
|
+
|
17
|
+
<% end %>
|
18
|
+
<% section.error_klasses.each do |error_klass| %>
|
19
|
+
### <%== render ViewComponent::DocsBuilderComponent::ErrorKlassDoc.new(error_klass, section.show_types) %>
|
16
20
|
|
17
21
|
<% end %>
|
18
22
|
<% end %>
|