view_component 2.35.0 → 2.39.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of view_component might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/app/controllers/view_components_controller.rb +1 -1
- data/docs/CHANGELOG.md +89 -0
- data/lib/rails/generators/abstract_generator.rb +19 -2
- data/lib/rails/generators/component/component_generator.rb +4 -0
- data/lib/rails/generators/component/templates/component.rb.tt +1 -1
- data/lib/rails/generators/erb/component_generator.rb +9 -0
- data/lib/rails/generators/erb/templates/component.html.erb.tt +1 -1
- data/lib/rails/generators/stimulus/component_generator.rb +26 -0
- data/lib/rails/generators/stimulus/templates/component_controller.js.tt +7 -0
- data/lib/view_component/base.rb +56 -13
- data/lib/view_component/collection.rb +4 -1
- data/lib/view_component/compiler.rb +40 -10
- data/lib/view_component/content_areas.rb +10 -3
- data/lib/view_component/engine.rb +13 -2
- data/lib/view_component/instrumentation.rb +5 -1
- data/lib/view_component/preview.rb +5 -1
- data/lib/view_component/slot_v2.rb +7 -1
- data/lib/view_component/slotable_v2.rb +40 -7
- data/lib/view_component/test_helpers.rb +7 -1
- data/lib/view_component/version.rb +1 -1
- data/lib/view_component/with_content_helper.rb +4 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e5f28b38a232a62dbe80033fe0ac5a7b0ee717f933afc76a1e991396ff51a8d6
|
4
|
+
data.tar.gz: ed6610e4fc417a876aeac464439cc627c5ce6e242a65faa0d7db001e2249035a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d7e130233a0699c54a1a9664c9a893afc6df8e45795d397ce6592e2edbd719c61ed6ef9fb373b3dc8d8285bc3a2d712c013579303138fb466806e646c2480465
|
7
|
+
data.tar.gz: 25001a6bff37068aceccfb029d5829cd759f4662f7f0b8eb4ab76a42bf0a31ba55f1ceb97480c4a285dc3e9d9da62e746194f623f6b443c647fdb17bb21b3963
|
@@ -56,7 +56,7 @@ class ViewComponentsController < Rails::ApplicationController # :nodoc:
|
|
56
56
|
if preview
|
57
57
|
@preview = ViewComponent::Preview.find(preview)
|
58
58
|
else
|
59
|
-
raise AbstractController::ActionNotFound, "Component preview '#{params[:path]}' not found"
|
59
|
+
raise AbstractController::ActionNotFound, "Component preview '#{params[:path]}' not found."
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
data/docs/CHANGELOG.md
CHANGED
@@ -7,6 +7,95 @@ title: Changelog
|
|
7
7
|
|
8
8
|
## main
|
9
9
|
|
10
|
+
## 2.39.0
|
11
|
+
|
12
|
+
* Clarify documentation of `with_variant` as an override of Action Pack.
|
13
|
+
|
14
|
+
*Blake Williams*, *Cameron Dutro*, *Joel Hawksley*
|
15
|
+
|
16
|
+
* Update docs page to be called Javascript and CSS, rename Building ViewComponents to Guide.
|
17
|
+
|
18
|
+
*Joel Hawksley*
|
19
|
+
|
20
|
+
* Deprecate `Base#with_variant`.
|
21
|
+
|
22
|
+
*Cameron Dutro*
|
23
|
+
|
24
|
+
* Error out in the CI if docs/api.md has to be regenerated.
|
25
|
+
|
26
|
+
*Dany Marcoux*
|
27
|
+
|
28
|
+
## 2.38.0
|
29
|
+
|
30
|
+
* Add `--stimulus` flag to the component generator. Generates a Stimulus controller alongside the component.
|
31
|
+
* Add config option `config.view_component.generate_stimulus_controller` to always generate a Stimulus controller.
|
32
|
+
|
33
|
+
*Sebastien Auriault*
|
34
|
+
|
35
|
+
## 2.37.0
|
36
|
+
|
37
|
+
* Clarify slots example in docs to reduce naming confusion.
|
38
|
+
|
39
|
+
*Joel Hawksley*, *Blake Williams*
|
40
|
+
|
41
|
+
* Fix error in documentation for `render_many` passthrough slots.
|
42
|
+
|
43
|
+
*Ollie Nye*
|
44
|
+
|
45
|
+
* Add test case for conflict with internal `@variant` variable.
|
46
|
+
|
47
|
+
*David Backeus*
|
48
|
+
|
49
|
+
* Document decision to not change naming convention recommendation to remove `-Component` suffix.
|
50
|
+
|
51
|
+
*Joel Hawksley*
|
52
|
+
|
53
|
+
* Fix typo in documentation.
|
54
|
+
|
55
|
+
*Ryo.gift*
|
56
|
+
|
57
|
+
* Add inline template example to benchmark script.
|
58
|
+
|
59
|
+
*Andrew Tait*
|
60
|
+
|
61
|
+
* Fix benchmark scripts.
|
62
|
+
|
63
|
+
*Andrew Tait*
|
64
|
+
|
65
|
+
* Run benchmarks in CI.
|
66
|
+
|
67
|
+
*Joel Hawksley*
|
68
|
+
|
69
|
+
## 2.36.0
|
70
|
+
|
71
|
+
* Add `slot_type` helper method.
|
72
|
+
|
73
|
+
*Jon Palmer*
|
74
|
+
|
75
|
+
* Add test case for rendering a ViewComponent with slots in a controller.
|
76
|
+
|
77
|
+
*Simon Fish*
|
78
|
+
|
79
|
+
* Add example ViewComponent to documentation landing page.
|
80
|
+
|
81
|
+
*Joel Hawksley*
|
82
|
+
|
83
|
+
* Set maximum line length to 120.
|
84
|
+
|
85
|
+
*Joel Hawksley*
|
86
|
+
|
87
|
+
* Setting a collection slot with the plural setter (`component.items(array)` for `renders_many :items`) returns the array of slots.
|
88
|
+
|
89
|
+
*Jon Palmer*
|
90
|
+
|
91
|
+
* Update error messages to be more descriptive and helpful.
|
92
|
+
|
93
|
+
*Joel Hawksley*
|
94
|
+
|
95
|
+
* Raise an error if the slot name for renders_many is :contents
|
96
|
+
|
97
|
+
*Simon Fish*
|
98
|
+
|
10
99
|
## 2.35.0
|
11
100
|
|
12
101
|
* Only load assets for Preview source highlighting if previews are enabled.
|
@@ -11,13 +11,21 @@ module ViewComponent
|
|
11
11
|
private
|
12
12
|
|
13
13
|
def destination
|
14
|
+
File.join(destination_directory, "#{destination_file_name}.html.#{engine_name}")
|
15
|
+
end
|
16
|
+
|
17
|
+
def destination_directory
|
14
18
|
if options["sidecar"]
|
15
|
-
File.join(component_path, class_path,
|
19
|
+
File.join(component_path, class_path, destination_file_name)
|
16
20
|
else
|
17
|
-
File.join(component_path, class_path
|
21
|
+
File.join(component_path, class_path)
|
18
22
|
end
|
19
23
|
end
|
20
24
|
|
25
|
+
def destination_file_name
|
26
|
+
"#{file_name}_component"
|
27
|
+
end
|
28
|
+
|
21
29
|
def file_name
|
22
30
|
@_file_name ||= super.sub(/_component\z/i, "")
|
23
31
|
end
|
@@ -25,5 +33,14 @@ module ViewComponent
|
|
25
33
|
def component_path
|
26
34
|
ViewComponent::Base.view_component_path
|
27
35
|
end
|
36
|
+
|
37
|
+
def stimulus_controller
|
38
|
+
if options["stimulus"]
|
39
|
+
File.join(destination_directory, destination_file_name).
|
40
|
+
sub("#{component_path}/", "").
|
41
|
+
gsub("_", "-").
|
42
|
+
gsub("/", "--")
|
43
|
+
end
|
44
|
+
end
|
28
45
|
end
|
29
46
|
end
|
@@ -12,6 +12,8 @@ module Rails
|
|
12
12
|
argument :attributes, type: :array, default: [], banner: "attribute"
|
13
13
|
check_class_collision suffix: "Component"
|
14
14
|
class_option :inline, type: :boolean, default: false
|
15
|
+
class_option :stimulus, type: :boolean, default: ViewComponent::Base.generate_stimulus_controller
|
16
|
+
class_option :sidecar, type: :boolean, default: false
|
15
17
|
|
16
18
|
def create_component_file
|
17
19
|
template "component.rb", File.join(component_path, class_path, "#{file_name}_component.rb")
|
@@ -21,6 +23,8 @@ module Rails
|
|
21
23
|
|
22
24
|
hook_for :preview, type: :boolean
|
23
25
|
|
26
|
+
hook_for :stimulus, type: :boolean
|
27
|
+
|
24
28
|
hook_for :template_engine do |instance, template_engine|
|
25
29
|
instance.invoke template_engine, [instance.name]
|
26
30
|
end
|
@@ -8,7 +8,7 @@ class <%= class_name %>Component < <%= parent_class %>
|
|
8
8
|
<%- end -%>
|
9
9
|
<%- if initialize_call_method_for_inline? -%>
|
10
10
|
def call
|
11
|
-
content_tag :h1, "Hello world!"
|
11
|
+
content_tag :h1, "Hello world!"<%= ", data: { controller: \"#{stimulus_controller}\" }" if options["stimulus"] %>
|
12
12
|
end
|
13
13
|
<%- end -%>
|
14
14
|
|
@@ -11,6 +11,7 @@ module Erb
|
|
11
11
|
source_root File.expand_path("templates", __dir__)
|
12
12
|
class_option :sidecar, type: :boolean, default: false
|
13
13
|
class_option :inline, type: :boolean, default: false
|
14
|
+
class_option :stimulus, type: :boolean, default: false
|
14
15
|
|
15
16
|
def engine_name
|
16
17
|
"erb"
|
@@ -19,6 +20,14 @@ module Erb
|
|
19
20
|
def copy_view_file
|
20
21
|
super
|
21
22
|
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def data_attributes
|
27
|
+
if options["stimulus"]
|
28
|
+
" data-controller=\"#{stimulus_controller}\""
|
29
|
+
end
|
30
|
+
end
|
22
31
|
end
|
23
32
|
end
|
24
33
|
end
|
@@ -1 +1 @@
|
|
1
|
-
<div
|
1
|
+
<div<%= data_attributes %>>Add <%= class_name %> template here</div>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Stimulus
|
4
|
+
module Generators
|
5
|
+
class ComponentGenerator < ::Rails::Generators::NamedBase
|
6
|
+
include ViewComponent::AbstractGenerator
|
7
|
+
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
9
|
+
class_option :sidecar, type: :boolean, default: false
|
10
|
+
|
11
|
+
def create_stimulus_controller
|
12
|
+
template "component_controller.js", destination
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def destination
|
18
|
+
if options["sidecar"]
|
19
|
+
File.join(component_path, class_path, "#{file_name}_component", "#{file_name}_component_controller.js")
|
20
|
+
else
|
21
|
+
File.join(component_path, class_path, "#{file_name}_component_controller.js")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/view_component/base.rb
CHANGED
@@ -86,7 +86,11 @@ module ViewComponent
|
|
86
86
|
@current_template = self
|
87
87
|
|
88
88
|
if block && defined?(@__vc_content_set_by_with_content)
|
89
|
-
raise ArgumentError.new(
|
89
|
+
raise ArgumentError.new(
|
90
|
+
"It looks like a block was provided after calling `with_content` on #{self.class.name}, " \
|
91
|
+
"which means that ViewComponent doesn't know which content to use.\n\n" \
|
92
|
+
"To fix this issue, use either `with_content` or a block."
|
93
|
+
)
|
90
94
|
end
|
91
95
|
|
92
96
|
@__vc_content_evaluated = false
|
@@ -110,7 +114,8 @@ module ViewComponent
|
|
110
114
|
""
|
111
115
|
end
|
112
116
|
|
113
|
-
# Called before rendering the component. Override to perform operations that
|
117
|
+
# Called before rendering the component. Override to perform operations that
|
118
|
+
# depend on having access to the view context, such as helpers.
|
114
119
|
#
|
115
120
|
# @return [void]
|
116
121
|
def before_render
|
@@ -150,20 +155,40 @@ module ViewComponent
|
|
150
155
|
end
|
151
156
|
end
|
152
157
|
|
153
|
-
# The current controller. Use sparingly as doing so introduces coupling
|
158
|
+
# The current controller. Use sparingly as doing so introduces coupling
|
159
|
+
# that inhibits encapsulation & reuse, often making testing difficult.
|
154
160
|
#
|
155
161
|
# @return [ActionController::Base]
|
156
162
|
def controller
|
157
|
-
|
163
|
+
if view_context.nil?
|
164
|
+
raise(
|
165
|
+
ViewContextCalledBeforeRenderError,
|
166
|
+
"`#controller` cannot be used during initialization, as it depends " \
|
167
|
+
"on the view context that only exists once a ViewComponent is passed to " \
|
168
|
+
"the Rails render pipeline.\n\n" \
|
169
|
+
"It's sometimes possible to fix this issue by moving code dependent on " \
|
170
|
+
"`#controller` to a `#before_render` method: https://viewcomponent.org/api.html#before_render--void."
|
171
|
+
)
|
172
|
+
end
|
158
173
|
|
159
174
|
@__vc_controller ||= view_context.controller
|
160
175
|
end
|
161
176
|
|
162
|
-
# A proxy through which to access helpers. Use sparingly as doing so introduces
|
177
|
+
# A proxy through which to access helpers. Use sparingly as doing so introduces
|
178
|
+
# coupling that inhibits encapsulation & reuse, often making testing difficult.
|
163
179
|
#
|
164
180
|
# @return [ActionView::Base]
|
165
181
|
def helpers
|
166
|
-
|
182
|
+
if view_context.nil?
|
183
|
+
raise(
|
184
|
+
ViewContextCalledBeforeRenderError,
|
185
|
+
"`#helpers` cannot be used during initialization, as it depends " \
|
186
|
+
"on the view context that only exists once a ViewComponent is passed to " \
|
187
|
+
"the Rails render pipeline.\n\n" \
|
188
|
+
"It's sometimes possible to fix this issue by moving code dependent on " \
|
189
|
+
"`#helpers` to a `#before_render` method: https://viewcomponent.org/api.html#before_render--void."
|
190
|
+
)
|
191
|
+
end
|
167
192
|
|
168
193
|
@__vc_helpers ||= controller.view_context
|
169
194
|
end
|
@@ -193,15 +218,21 @@ module ViewComponent
|
|
193
218
|
|
194
219
|
# Use the provided variant instead of the one determined by the current request.
|
195
220
|
#
|
221
|
+
# @deprecated Will be removed in v3.0.0.
|
196
222
|
# @param variant [Symbol] The variant to be used by the component.
|
197
223
|
# @return [self]
|
198
224
|
def with_variant(variant)
|
225
|
+
ActiveSupport::Deprecation.warn(
|
226
|
+
"`with_variant` is deprecated and will be removed in ViewComponent v3.0.0."
|
227
|
+
)
|
228
|
+
|
199
229
|
@__vc_variant = variant
|
200
230
|
|
201
231
|
self
|
202
232
|
end
|
203
233
|
|
204
|
-
# The current request. Use sparingly as doing so introduces coupling that
|
234
|
+
# The current request. Use sparingly as doing so introduces coupling that
|
235
|
+
# inhibits encapsulation & reuse, often making testing difficult.
|
205
236
|
#
|
206
237
|
# @return [ActionDispatch::Request]
|
207
238
|
def request
|
@@ -252,6 +283,14 @@ module ViewComponent
|
|
252
283
|
#
|
253
284
|
mattr_accessor :show_previews_source, instance_writer: false, default: false
|
254
285
|
|
286
|
+
# Always generate a Stimulus controller alongside the component:
|
287
|
+
#
|
288
|
+
# config.view_component.generate_stimulus_controller = true
|
289
|
+
#
|
290
|
+
# Defaults to `false`.
|
291
|
+
#
|
292
|
+
mattr_accessor :generate_stimulus_controller, instance_writer: false, default: false
|
293
|
+
|
255
294
|
# Path for component files
|
256
295
|
#
|
257
296
|
# config.view_component.view_component_path = "app/my_components"
|
@@ -409,13 +448,18 @@ module ViewComponent
|
|
409
448
|
# the component.
|
410
449
|
if initialize_parameters.empty?
|
411
450
|
raise ArgumentError.new(
|
412
|
-
"#{self} initializer is empty or invalid."
|
451
|
+
"The #{self} initializer is empty or invalid." \
|
452
|
+
"It must accept the parameter `#{parameter}` to render it as a collection.\n\n" \
|
453
|
+
"To fix this issue, update the initializer to accept `#{parameter}`.\n\n" \
|
454
|
+
"See https://viewcomponent.org/guide/collections.html for more information on rendering collections."
|
413
455
|
)
|
414
456
|
end
|
415
457
|
|
416
458
|
raise ArgumentError.new(
|
417
|
-
"#{self}
|
418
|
-
"
|
459
|
+
"The initializer for #{self} does not accept the parameter `#{parameter}`, " \
|
460
|
+
"which is required in order to render it as a collection.\n\n" \
|
461
|
+
"To fix this issue, update the initializer to accept `#{parameter}`.\n\n" \
|
462
|
+
"See https://viewcomponent.org/guide/collections.html for more information on rendering collections."
|
419
463
|
)
|
420
464
|
end
|
421
465
|
|
@@ -427,9 +471,8 @@ module ViewComponent
|
|
427
471
|
return unless initialize_parameter_names.include?(RESERVED_PARAMETER)
|
428
472
|
|
429
473
|
raise ViewComponent::ComponentError.new(
|
430
|
-
"#{self} initializer cannot
|
431
|
-
"
|
432
|
-
"public ViewComponent method."
|
474
|
+
"#{self} initializer cannot accept the parameter `#{RESERVED_PARAMETER}`, as it will override a " \
|
475
|
+
"public ViewComponent method. To fix this issue, rename the parameter."
|
433
476
|
)
|
434
477
|
end
|
435
478
|
|
@@ -32,7 +32,10 @@ module ViewComponent
|
|
32
32
|
if object.respond_to?(:to_ary)
|
33
33
|
object.to_ary
|
34
34
|
else
|
35
|
-
raise ArgumentError.new(
|
35
|
+
raise ArgumentError.new(
|
36
|
+
"The value of the first argument passed to `with_collection` isn't a valid collection. " \
|
37
|
+
"Make sure it responds to `to_ary`."
|
38
|
+
)
|
36
39
|
end
|
37
40
|
end
|
38
41
|
|
@@ -16,7 +16,10 @@ module ViewComponent
|
|
16
16
|
subclass_instance_methods = component_class.instance_methods(false)
|
17
17
|
|
18
18
|
if subclass_instance_methods.include?(:with_content) && raise_errors
|
19
|
-
raise ViewComponent::ComponentError.new(
|
19
|
+
raise ViewComponent::ComponentError.new(
|
20
|
+
"#{component_class} implements a reserved method, `#with_content`.\n\n" \
|
21
|
+
"To fix this issue, change the name of the method."
|
22
|
+
)
|
20
23
|
end
|
21
24
|
|
22
25
|
if template_errors.present?
|
@@ -27,7 +30,8 @@ module ViewComponent
|
|
27
30
|
|
28
31
|
if subclass_instance_methods.include?(:before_render_check)
|
29
32
|
ActiveSupport::Deprecation.warn(
|
30
|
-
"
|
33
|
+
"`#before_render_check` will be removed in v3.0.0.\n\n" \
|
34
|
+
"To fix this issue, use `#before_render` instead."
|
31
35
|
)
|
32
36
|
end
|
33
37
|
|
@@ -40,7 +44,10 @@ module ViewComponent
|
|
40
44
|
# Remove existing compiled template methods,
|
41
45
|
# as Ruby warns when redefining a method.
|
42
46
|
method_name = call_method_name(template[:variant])
|
43
|
-
|
47
|
+
|
48
|
+
if component_class.instance_methods.include?(method_name.to_sym)
|
49
|
+
component_class.send(:undef_method, method_name.to_sym)
|
50
|
+
end
|
44
51
|
|
45
52
|
component_class.class_eval <<-RUBY, template[:path], -1
|
46
53
|
def #{method_name}
|
@@ -62,7 +69,9 @@ module ViewComponent
|
|
62
69
|
attr_reader :component_class
|
63
70
|
|
64
71
|
def define_render_template_for
|
65
|
-
|
72
|
+
if component_class.instance_methods.include?(:render_template_for)
|
73
|
+
component_class.send(:undef_method, :render_template_for)
|
74
|
+
end
|
66
75
|
|
67
76
|
variant_elsifs = variants.compact.uniq.map do |variant|
|
68
77
|
"elsif variant.to_sym == :#{variant}\n #{call_method_name(variant)}"
|
@@ -90,7 +99,9 @@ module ViewComponent
|
|
90
99
|
end
|
91
100
|
|
92
101
|
if templates.count { |template| template[:variant].nil? } > 1
|
93
|
-
errors <<
|
102
|
+
errors <<
|
103
|
+
"More than one template found for #{component_class}. " \
|
104
|
+
"There can only be one default template file per component."
|
94
105
|
end
|
95
106
|
|
96
107
|
invalid_variants =
|
@@ -101,11 +112,16 @@ module ViewComponent
|
|
101
112
|
sort
|
102
113
|
|
103
114
|
unless invalid_variants.empty?
|
104
|
-
errors <<
|
115
|
+
errors <<
|
116
|
+
"More than one template found for #{'variant'.pluralize(invalid_variants.count)} " \
|
117
|
+
"#{invalid_variants.map { |v| "'#{v}'" }.to_sentence} in #{component_class}. " \
|
118
|
+
"There can only be one template file per variant."
|
105
119
|
end
|
106
120
|
|
107
121
|
if templates.find { |template| template[:variant].nil? } && inline_calls_defined_on_self.include?(:call)
|
108
|
-
errors <<
|
122
|
+
errors <<
|
123
|
+
"Template file and inline render method found for #{component_class}. " \
|
124
|
+
"There can only be a template file or inline render method per component."
|
109
125
|
end
|
110
126
|
|
111
127
|
duplicate_template_file_and_inline_variant_calls =
|
@@ -114,7 +130,12 @@ module ViewComponent
|
|
114
130
|
unless duplicate_template_file_and_inline_variant_calls.empty?
|
115
131
|
count = duplicate_template_file_and_inline_variant_calls.count
|
116
132
|
|
117
|
-
errors <<
|
133
|
+
errors <<
|
134
|
+
"Template #{'file'.pluralize(count)} and inline render #{'method'.pluralize(count)} " \
|
135
|
+
"found for #{'variant'.pluralize(count)} " \
|
136
|
+
"#{duplicate_template_file_and_inline_variant_calls.map { |v| "'#{v}'" }.to_sentence} " \
|
137
|
+
"in #{component_class}. " \
|
138
|
+
"There can only be a template file or inline render method per variant."
|
118
139
|
end
|
119
140
|
|
120
141
|
errors
|
@@ -143,7 +164,10 @@ module ViewComponent
|
|
143
164
|
# Fetch only ViewComponent ancestor classes to limit the scope of
|
144
165
|
# finding inline calls
|
145
166
|
view_component_ancestors =
|
146
|
-
|
167
|
+
(
|
168
|
+
component_class.ancestors.take_while { |ancestor| ancestor != ViewComponent::Base } -
|
169
|
+
component_class.included_modules
|
170
|
+
)
|
147
171
|
|
148
172
|
view_component_ancestors.flat_map { |ancestor| ancestor.instance_methods(false).grep(/^call/) }.uniq
|
149
173
|
end
|
@@ -172,7 +196,13 @@ module ViewComponent
|
|
172
196
|
if handler.method(:call).parameters.length > 1
|
173
197
|
handler.call(component_class, template)
|
174
198
|
else
|
175
|
-
handler.call(
|
199
|
+
handler.call(
|
200
|
+
OpenStruct.new(
|
201
|
+
source: template,
|
202
|
+
identifier: component_class.identifier,
|
203
|
+
type: component_class.type
|
204
|
+
)
|
205
|
+
)
|
176
206
|
end
|
177
207
|
end
|
178
208
|
|
@@ -14,7 +14,11 @@ module ViewComponent
|
|
14
14
|
# @private
|
15
15
|
def with(area, content = nil, &block)
|
16
16
|
unless content_areas.include?(area)
|
17
|
-
raise ArgumentError.new
|
17
|
+
raise ArgumentError.new(
|
18
|
+
"Unknown content_area '#{area}' for #{self} - expected one of '#{content_areas}'.\n\n" \
|
19
|
+
"To fix this issue, add `with_content_area :#{area}` to #{self} or reference " \
|
20
|
+
"a valid content area."
|
21
|
+
)
|
18
22
|
end
|
19
23
|
|
20
24
|
if block_given?
|
@@ -28,12 +32,15 @@ module ViewComponent
|
|
28
32
|
class_methods do
|
29
33
|
def with_content_areas(*areas)
|
30
34
|
ActiveSupport::Deprecation.warn(
|
31
|
-
"`with_content_areas` is deprecated and will be removed in ViewComponent v3.0.0.\n" \
|
35
|
+
"`with_content_areas` is deprecated and will be removed in ViewComponent v3.0.0.\n\n" \
|
32
36
|
"Use slots (https://viewcomponent.org/guide/slots.html) instead."
|
33
37
|
)
|
34
38
|
|
35
39
|
if areas.include?(:content)
|
36
|
-
raise ArgumentError.new
|
40
|
+
raise ArgumentError.new(
|
41
|
+
"#{self} defines a content area called :content, which is a reserved name. \n\n" \
|
42
|
+
"To fix this issue, use another name, such as `:body`."
|
43
|
+
)
|
37
44
|
end
|
38
45
|
|
39
46
|
areas.each do |area|
|
@@ -110,8 +110,19 @@ module ViewComponent
|
|
110
110
|
app.routes.prepend do
|
111
111
|
preview_controller = options.preview_controller.sub(/Controller$/, "").underscore
|
112
112
|
|
113
|
-
get
|
114
|
-
|
113
|
+
get(
|
114
|
+
options.preview_route,
|
115
|
+
to: "#{preview_controller}#index",
|
116
|
+
as: :preview_view_components,
|
117
|
+
internal: true
|
118
|
+
)
|
119
|
+
|
120
|
+
get(
|
121
|
+
"#{options.preview_route}/*path",
|
122
|
+
to: "#{preview_controller}#previews",
|
123
|
+
as: :preview_view_component,
|
124
|
+
internal: true
|
125
|
+
)
|
115
126
|
end
|
116
127
|
end
|
117
128
|
|
@@ -9,7 +9,11 @@ module ViewComponent # :nodoc:
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def render_in(view_context, &block)
|
12
|
-
ActiveSupport::Notifications.instrument(
|
12
|
+
ActiveSupport::Notifications.instrument(
|
13
|
+
"!render.view_component",
|
14
|
+
name: self.class.name,
|
15
|
+
identifier: self.class.identifier
|
16
|
+
) do
|
13
17
|
super(view_context, &block)
|
14
18
|
end
|
15
19
|
end
|
@@ -77,7 +77,11 @@ module ViewComponent # :nodoc:
|
|
77
77
|
end
|
78
78
|
|
79
79
|
if preview_path.nil?
|
80
|
-
raise
|
80
|
+
raise(
|
81
|
+
PreviewTemplateError,
|
82
|
+
"A preview template for example #{example} does not exist.\n\n" \
|
83
|
+
"To fix this issue, create a template for the example."
|
84
|
+
)
|
81
85
|
end
|
82
86
|
|
83
87
|
path = Dir["#{preview_path}/#{preview_name}_preview/#{example}.html.*"].first
|
@@ -30,7 +30,13 @@ module ViewComponent
|
|
30
30
|
|
31
31
|
view_context = @parent.send(:view_context)
|
32
32
|
|
33
|
-
|
33
|
+
if defined?(@__vc_content_block) && defined?(@__vc_content_set_by_with_content)
|
34
|
+
raise ArgumentError.new(
|
35
|
+
"It looks like a block was provided after calling `with_content` on #{self.class.name}, " \
|
36
|
+
"which means that ViewComponent doesn't know which content to use.\n\n" \
|
37
|
+
"To fix this issue, use either `with_content` or a block."
|
38
|
+
)
|
39
|
+
end
|
34
40
|
|
35
41
|
@content =
|
36
42
|
if defined?(@__vc_component_instance)
|
@@ -65,7 +65,7 @@ module ViewComponent
|
|
65
65
|
# <% end %>
|
66
66
|
# <% end %>
|
67
67
|
def renders_one(slot_name, callable = nil)
|
68
|
-
|
68
|
+
validate_singular_slot_name(slot_name)
|
69
69
|
|
70
70
|
define_method slot_name do |*args, **kwargs, &block|
|
71
71
|
if args.empty? && kwargs.empty? && block.nil?
|
@@ -116,7 +116,7 @@ module ViewComponent
|
|
116
116
|
# <% end %>
|
117
117
|
# <% end %>
|
118
118
|
def renders_many(slot_name, callable = nil)
|
119
|
-
|
119
|
+
validate_plural_slot_name(slot_name)
|
120
120
|
|
121
121
|
singular_name = ActiveSupport::Inflector.singularize(slot_name)
|
122
122
|
|
@@ -133,7 +133,7 @@ module ViewComponent
|
|
133
133
|
if collection_args.nil? && block.nil?
|
134
134
|
get_slot(slot_name)
|
135
135
|
else
|
136
|
-
collection_args.
|
136
|
+
collection_args.map do |args|
|
137
137
|
set_slot(slot_name, **args, &block)
|
138
138
|
end
|
139
139
|
end
|
@@ -142,6 +142,17 @@ module ViewComponent
|
|
142
142
|
register_slot(slot_name, collection: true, callable: callable)
|
143
143
|
end
|
144
144
|
|
145
|
+
def slot_type(slot_name)
|
146
|
+
registered_slot = registered_slots[slot_name]
|
147
|
+
if registered_slot
|
148
|
+
registered_slot[:collection] ? :collection : :single
|
149
|
+
else
|
150
|
+
plural_slot_name = ActiveSupport::Inflector.pluralize(slot_name).to_sym
|
151
|
+
plural_registered_slot = registered_slots[plural_slot_name]
|
152
|
+
plural_registered_slot&.fetch(:collection) ? :collection_item : nil
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
145
156
|
# Clone slot configuration into child class
|
146
157
|
# see #test_slots_pollution
|
147
158
|
def inherited(child)
|
@@ -174,14 +185,35 @@ module ViewComponent
|
|
174
185
|
self.registered_slots[slot_name] = slot
|
175
186
|
end
|
176
187
|
|
177
|
-
def
|
188
|
+
def validate_plural_slot_name(slot_name)
|
189
|
+
if slot_name.to_sym == :contents
|
190
|
+
raise ArgumentError.new(
|
191
|
+
"#{self} declares a slot named #{slot_name}, which is a reserved word in the ViewComponent framework.\n\n" \
|
192
|
+
"To fix this issue, choose a different name."
|
193
|
+
)
|
194
|
+
end
|
195
|
+
|
196
|
+
raise_if_slot_registered(slot_name)
|
197
|
+
end
|
198
|
+
|
199
|
+
def validate_singular_slot_name(slot_name)
|
178
200
|
if slot_name.to_sym == :content
|
179
|
-
raise ArgumentError.new(
|
201
|
+
raise ArgumentError.new(
|
202
|
+
"#{self} declares a slot named #{slot_name}, which is a reserved word in the ViewComponent framework.\n\n" \
|
203
|
+
"To fix this issue, choose a different name."
|
204
|
+
)
|
180
205
|
end
|
181
206
|
|
207
|
+
raise_if_slot_registered(slot_name)
|
208
|
+
end
|
209
|
+
|
210
|
+
def raise_if_slot_registered(slot_name)
|
182
211
|
if self.registered_slots.key?(slot_name)
|
183
212
|
# TODO remove? This breaks overriding slots when slots are inherited
|
184
|
-
raise ArgumentError.new(
|
213
|
+
raise ArgumentError.new(
|
214
|
+
"#{self} declares the #{slot_name} slot multiple times.\n\n" \
|
215
|
+
"To fix this issue, choose a different slot name."
|
216
|
+
)
|
185
217
|
end
|
186
218
|
end
|
187
219
|
end
|
@@ -224,7 +256,8 @@ module ViewComponent
|
|
224
256
|
slot.__vc_component_instance = slot_definition[:renderable].new(*args, **kwargs)
|
225
257
|
# If class name as a string
|
226
258
|
elsif slot_definition[:renderable_class_name]
|
227
|
-
slot.__vc_component_instance =
|
259
|
+
slot.__vc_component_instance =
|
260
|
+
self.class.const_get(slot_definition[:renderable_class_name]).new(*args, **kwargs)
|
228
261
|
# If passed a lambda
|
229
262
|
elsif slot_definition[:renderable_function]
|
230
263
|
# Use `bind(self)` to ensure lambda is executed in the context of the
|
@@ -17,7 +17,13 @@ module ViewComponent
|
|
17
17
|
# We don't have a test case for running an application without capybara installed.
|
18
18
|
# It's probably fine to leave this without coverage.
|
19
19
|
# :nocov:
|
20
|
-
|
20
|
+
if ENV["DEBUG"]
|
21
|
+
warn(
|
22
|
+
"WARNING in `ViewComponent::TestHelpers`: You must add `capybara` " \
|
23
|
+
"to your Gemfile to use Capybara assertions."
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
21
27
|
# :nocov:
|
22
28
|
end
|
23
29
|
|
@@ -4,7 +4,10 @@ module ViewComponent
|
|
4
4
|
module WithContentHelper
|
5
5
|
def with_content(value)
|
6
6
|
if value.nil?
|
7
|
-
raise ArgumentError.new(
|
7
|
+
raise ArgumentError.new(
|
8
|
+
"No content provided to `#with_content` for #{self}.\n\n" \
|
9
|
+
"To fix this issue, pass a value."
|
10
|
+
)
|
8
11
|
else
|
9
12
|
@__vc_content_set_by_with_content = value
|
10
13
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: view_component
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.39.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitHub Open Source
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-08-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -287,6 +287,8 @@ files:
|
|
287
287
|
- lib/rails/generators/rspec/templates/component_spec.rb.tt
|
288
288
|
- lib/rails/generators/slim/component_generator.rb
|
289
289
|
- lib/rails/generators/slim/templates/component.html.slim.tt
|
290
|
+
- lib/rails/generators/stimulus/component_generator.rb
|
291
|
+
- lib/rails/generators/stimulus/templates/component_controller.js.tt
|
290
292
|
- lib/rails/generators/test_unit/component_generator.rb
|
291
293
|
- lib/rails/generators/test_unit/templates/component_test.rb.tt
|
292
294
|
- lib/view_component.rb
|