view_component 2.39.0 → 2.43.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/README.md +1 -1
- data/docs/CHANGELOG.md +123 -2
- data/lib/rails/generators/component/component_generator.rb +8 -1
- data/lib/rails/generators/stimulus/component_generator.rb +13 -0
- data/lib/rails/generators/stimulus/templates/component_controller.js.tt +1 -1
- data/lib/view_component/base.rb +30 -23
- data/lib/view_component/engine.rb +5 -1
- data/lib/view_component/polymorphic_slots.rb +74 -0
- data/lib/view_component/preview.rb +1 -0
- data/lib/view_component/rails/tasks/view_component.rake +11 -0
- data/lib/view_component/slot_v2.rb +3 -2
- data/lib/view_component/slotable_v2.rb +32 -20
- data/lib/view_component/test_helpers.rb +45 -0
- data/lib/view_component/version.rb +1 -1
- data/lib/view_component.rb +2 -0
- metadata +18 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3213b43fb64df68370b007c5e90029923c1b35d5893bfb1c57fad904d2df99fc
|
4
|
+
data.tar.gz: d13ad45c178645ba3c639cd889ec9f645defe96579f0a94d305b0ce940405bb1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 159ff39bb666465d3ae385b9abed08c977c748d4c4c2d2112c1dd87b7c2823a819d253af78a9b14ead1519bbd15c79de4c92518266ebe2fd88fd9de4850bf887
|
7
|
+
data.tar.gz: 7a62698b2449485ac139b89706c785f1466b49e98551df4a70897a347f8cc64619a072b860ebdd09869728978699e7926e3a09649abe083b954d4e18c746cc18
|
data/README.md
CHANGED
data/docs/CHANGELOG.md
CHANGED
@@ -7,6 +7,127 @@ title: Changelog
|
|
7
7
|
|
8
8
|
## main
|
9
9
|
|
10
|
+
## 2.43.0
|
11
|
+
|
12
|
+
* Add note about tests and instance methods.
|
13
|
+
|
14
|
+
*Joel Hawksley*
|
15
|
+
|
16
|
+
* Flesh out `ViewComponents in practice`.
|
17
|
+
|
18
|
+
*Joel Hawksley*
|
19
|
+
|
20
|
+
* Add CODEOWNERS entries for feature areas owned by community committers.
|
21
|
+
|
22
|
+
*Joel Hawksley*
|
23
|
+
|
24
|
+
* Separate lint and CI workflows.
|
25
|
+
|
26
|
+
*Blake Williams*
|
27
|
+
|
28
|
+
* Add support for `image_path` helper in previews.
|
29
|
+
|
30
|
+
*Tobias Ahlin*, *Joel Hawksley*
|
31
|
+
|
32
|
+
* Add section to docs listing users of ViewComponent. Please submit a PR to add your team to the list!
|
33
|
+
|
34
|
+
*Joel Hawksley*
|
35
|
+
|
36
|
+
* Fix loading issue with Stimulus generator and add specs for Stimulus generator.
|
37
|
+
|
38
|
+
*Peter Sumskas*
|
39
|
+
|
40
|
+
* Remove dependency on `ActionDispatch::Static` in Rails middleware stack when enabling statics assets for source code preview.
|
41
|
+
|
42
|
+
*Gregory Igelmund*
|
43
|
+
|
44
|
+
* Require `view_component/engine` automatically.
|
45
|
+
|
46
|
+
*Cameron Dutro*
|
47
|
+
|
48
|
+
## 2.42.0
|
49
|
+
|
50
|
+
* Add logo files and page to docs.
|
51
|
+
|
52
|
+
*Dylan Smith*
|
53
|
+
|
54
|
+
* Add `ViewComponents in practice` documentation.
|
55
|
+
|
56
|
+
*Joel Hawksley*
|
57
|
+
|
58
|
+
* Fix bug where calling lambda slots without arguments would break in Ruby < 2.7.
|
59
|
+
|
60
|
+
*Manuel Puyol*
|
61
|
+
|
62
|
+
* Improve Stimulus controller template to import from `stimulus` or `@hotwired/stimulus`.
|
63
|
+
|
64
|
+
*Mario Schüttel*
|
65
|
+
|
66
|
+
* Fix bug where `helpers` would instantiate and use a new `view_context` in each component.
|
67
|
+
|
68
|
+
*Blake Williams*, *Ian C. Anderson*
|
69
|
+
|
70
|
+
* Implement polymorphic slots as experimental feature. See the Slots documentation to learn more.
|
71
|
+
|
72
|
+
*Cameron Dutro*
|
73
|
+
|
74
|
+
## 2.41.0
|
75
|
+
|
76
|
+
* Add `sprockets-rails` development dependency to fix test suite failures when using rails@main.
|
77
|
+
|
78
|
+
*Blake Williams*
|
79
|
+
|
80
|
+
* Fix Ruby indentation warning.
|
81
|
+
|
82
|
+
*Blake Williams*
|
83
|
+
|
84
|
+
* Add `--parent` generator option to specify the parent class.
|
85
|
+
* Add config option `config.view_component.component_parent_class` to change it project-wide.
|
86
|
+
|
87
|
+
*Hans Lemuet*
|
88
|
+
|
89
|
+
* Update docs to add example for using Devise helpers in tests.
|
90
|
+
|
91
|
+
*Matthew Rider*
|
92
|
+
|
93
|
+
* Fix bug where `with_collection_parameter` did not inherit from parent component.
|
94
|
+
|
95
|
+
*Will Drexler*, *Christian Campoli*
|
96
|
+
|
97
|
+
* Allow query parameters in `with_request_url` test helper.
|
98
|
+
|
99
|
+
*Javi Martín*
|
100
|
+
|
101
|
+
* Add "how to render a component to a string" to FAQ.
|
102
|
+
|
103
|
+
*Hans Lemuet*
|
104
|
+
|
105
|
+
* Add `#render_in` to API docs.
|
106
|
+
|
107
|
+
*Hans Lemuet*
|
108
|
+
|
109
|
+
* Forward keyword arguments from slot wrapper to component instance using ruby2_keywords.
|
110
|
+
|
111
|
+
*Cameron Dutro*
|
112
|
+
|
113
|
+
## 2.40.0
|
114
|
+
|
115
|
+
* Replace antipatterns section in the documentation with best practices.
|
116
|
+
|
117
|
+
*Blake Williams*
|
118
|
+
|
119
|
+
* Add components to `rails stats` task.
|
120
|
+
|
121
|
+
*Nicolas Brousse*
|
122
|
+
|
123
|
+
* Fix bug when using Slim and writing a slot whose block evaluates to `nil`.
|
124
|
+
|
125
|
+
*Yousuf Jukaku*
|
126
|
+
|
127
|
+
* Add documentation for test helpers.
|
128
|
+
|
129
|
+
*Joel Hawksley*
|
130
|
+
|
10
131
|
## 2.39.0
|
11
132
|
|
12
133
|
* Clarify documentation of `with_variant` as an override of Action Pack.
|
@@ -44,7 +165,7 @@ title: Changelog
|
|
44
165
|
|
45
166
|
* Add test case for conflict with internal `@variant` variable.
|
46
167
|
|
47
|
-
|
168
|
+
*David Backeus*
|
48
169
|
|
49
170
|
* Document decision to not change naming convention recommendation to remove `-Component` suffix.
|
50
171
|
|
@@ -108,7 +229,7 @@ title: Changelog
|
|
108
229
|
|
109
230
|
* Ensure consistent indentation with Rubocop.
|
110
231
|
|
111
|
-
*Joel Hawksley
|
232
|
+
*Joel Hawksley*
|
112
233
|
|
113
234
|
* Bump `activesupport` upper bound from `< 7.0` to `< 8.0`.
|
114
235
|
|
@@ -12,6 +12,7 @@ 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 :parent, type: :string, desc: "The parent class for the generated component"
|
15
16
|
class_option :stimulus, type: :boolean, default: ViewComponent::Base.generate_stimulus_controller
|
16
17
|
class_option :sidecar, type: :boolean, default: false
|
17
18
|
|
@@ -32,7 +33,9 @@ module Rails
|
|
32
33
|
private
|
33
34
|
|
34
35
|
def parent_class
|
35
|
-
|
36
|
+
return options[:parent] if options[:parent]
|
37
|
+
|
38
|
+
ViewComponent::Base.component_parent_class || default_parent_class
|
36
39
|
end
|
37
40
|
|
38
41
|
def initialize_signature
|
@@ -48,6 +51,10 @@ module Rails
|
|
48
51
|
def initialize_call_method_for_inline?
|
49
52
|
options["inline"]
|
50
53
|
end
|
54
|
+
|
55
|
+
def default_parent_class
|
56
|
+
defined?(ApplicationComponent) ? ApplicationComponent : ViewComponent::Base
|
57
|
+
end
|
51
58
|
end
|
52
59
|
end
|
53
60
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "rails/generators/abstract_generator"
|
4
|
+
|
3
5
|
module Stimulus
|
4
6
|
module Generators
|
5
7
|
class ComponentGenerator < ::Rails::Generators::NamedBase
|
@@ -12,6 +14,12 @@ module Stimulus
|
|
12
14
|
template "component_controller.js", destination
|
13
15
|
end
|
14
16
|
|
17
|
+
def stimulus_module
|
18
|
+
return "stimulus" if legacy_stimulus?
|
19
|
+
|
20
|
+
"@hotwired/stimulus"
|
21
|
+
end
|
22
|
+
|
15
23
|
private
|
16
24
|
|
17
25
|
def destination
|
@@ -21,6 +29,11 @@ module Stimulus
|
|
21
29
|
File.join(component_path, class_path, "#{file_name}_component_controller.js")
|
22
30
|
end
|
23
31
|
end
|
32
|
+
|
33
|
+
def legacy_stimulus?
|
34
|
+
package_json_pathname = Rails.root.join("package.json")
|
35
|
+
package_json_pathname.exist? && JSON.parse(package_json_pathname.read).dig("dependencies", "stimulus").present?
|
36
|
+
end
|
24
37
|
end
|
25
38
|
end
|
26
39
|
end
|
data/lib/view_component/base.rb
CHANGED
@@ -5,6 +5,7 @@ require "active_support/configurable"
|
|
5
5
|
require "view_component/collection"
|
6
6
|
require "view_component/compile_cache"
|
7
7
|
require "view_component/content_areas"
|
8
|
+
require "view_component/polymorphic_slots"
|
8
9
|
require "view_component/previewable"
|
9
10
|
require "view_component/slotable"
|
10
11
|
require "view_component/slotable_v2"
|
@@ -28,6 +29,8 @@ module ViewComponent
|
|
28
29
|
class_attribute :content_areas
|
29
30
|
self.content_areas = [] # class_attribute:default doesn't work until Rails 5.2
|
30
31
|
|
32
|
+
attr_accessor :original_view_context
|
33
|
+
|
31
34
|
# EXPERIMENTAL: This API is experimental and may be removed at any time.
|
32
35
|
# Hook for allowing components to do work as part of the compilation process.
|
33
36
|
#
|
@@ -39,33 +42,18 @@ module ViewComponent
|
|
39
42
|
|
40
43
|
# Entrypoint for rendering components.
|
41
44
|
#
|
42
|
-
# view_context
|
43
|
-
# block
|
44
|
-
#
|
45
|
-
# returns HTML that has been escaped by the respective template handler
|
46
|
-
#
|
47
|
-
# Example subclass:
|
48
|
-
#
|
49
|
-
# app/components/my_component.rb:
|
50
|
-
# class MyComponent < ViewComponent::Base
|
51
|
-
# def initialize(title:)
|
52
|
-
# @title = title
|
53
|
-
# end
|
54
|
-
# end
|
45
|
+
# - `view_context`: ActionView context from calling view
|
46
|
+
# - `block`: optional block to be captured within the view context
|
55
47
|
#
|
56
|
-
#
|
57
|
-
# <span title="<%= @title %>">Hello, <%= content %>!</span>
|
48
|
+
# Returns HTML that has been escaped by the respective template handler.
|
58
49
|
#
|
59
|
-
#
|
60
|
-
# <%= render MyComponent.new(title: "greeting") do %>world<% end %>
|
61
|
-
# returns:
|
62
|
-
# <span title="greeting">Hello, world!</span>
|
63
|
-
#
|
64
|
-
# @private
|
50
|
+
# @return [String]
|
65
51
|
def render_in(view_context, &block)
|
66
52
|
self.class.compile(raise_errors: true)
|
67
53
|
|
68
54
|
@view_context = view_context
|
55
|
+
self.original_view_context ||= view_context
|
56
|
+
|
69
57
|
@lookup_context ||= view_context.lookup_context
|
70
58
|
|
71
59
|
# required for path helpers in older Rails versions
|
@@ -149,9 +137,10 @@ module ViewComponent
|
|
149
137
|
# @private
|
150
138
|
def render(options = {}, args = {}, &block)
|
151
139
|
if options.is_a? ViewComponent::Base
|
140
|
+
options.original_view_context = original_view_context
|
152
141
|
super
|
153
142
|
else
|
154
|
-
|
143
|
+
original_view_context.render(options, args, &block)
|
155
144
|
end
|
156
145
|
end
|
157
146
|
|
@@ -190,7 +179,14 @@ module ViewComponent
|
|
190
179
|
)
|
191
180
|
end
|
192
181
|
|
193
|
-
|
182
|
+
# Attempt to re-use the original view_context passed to the first
|
183
|
+
# component rendered in the rendering pipeline. This prevents the
|
184
|
+
# instantiation of a new view_context via `controller.view_context` which
|
185
|
+
# always returns a new instance of the view context class.
|
186
|
+
#
|
187
|
+
# This allows ivars to remain persisted when using the same helper via
|
188
|
+
# `helpers` across multiple components and partials.
|
189
|
+
@__vc_helpers ||= original_view_context || controller.view_context
|
194
190
|
end
|
195
191
|
|
196
192
|
# Exposes .virtual_path as an instance method
|
@@ -298,6 +294,14 @@ module ViewComponent
|
|
298
294
|
# Defaults to "app/components".
|
299
295
|
mattr_accessor :view_component_path, instance_writer: false, default: "app/components"
|
300
296
|
|
297
|
+
# Parent class for generated components
|
298
|
+
#
|
299
|
+
# config.view_component.component_parent_class = "MyBaseComponent"
|
300
|
+
#
|
301
|
+
# Defaults to "ApplicationComponent" if defined, "ViewComponent::Base" otherwise.
|
302
|
+
mattr_accessor :component_parent_class,
|
303
|
+
instance_writer: false
|
304
|
+
|
301
305
|
class << self
|
302
306
|
# @private
|
303
307
|
attr_accessor :source_location, :virtual_path
|
@@ -384,6 +388,9 @@ module ViewComponent
|
|
384
388
|
%r{(.*#{Regexp.quote(ViewComponent::Base.view_component_path)})|(\.rb)}, ""
|
385
389
|
)
|
386
390
|
|
391
|
+
# Set collection parameter to the extended component
|
392
|
+
child.with_collection_parameter provided_collection_parameter
|
393
|
+
|
387
394
|
super
|
388
395
|
end
|
389
396
|
|
@@ -8,6 +8,10 @@ module ViewComponent
|
|
8
8
|
config.view_component = ActiveSupport::OrderedOptions.new
|
9
9
|
config.view_component.preview_paths ||= []
|
10
10
|
|
11
|
+
rake_tasks do
|
12
|
+
load "view_component/rails/tasks/view_component.rake"
|
13
|
+
end
|
14
|
+
|
11
15
|
initializer "view_component.set_configs" do |app|
|
12
16
|
options = app.config.view_component
|
13
17
|
|
@@ -99,7 +103,7 @@ module ViewComponent
|
|
99
103
|
|
100
104
|
initializer "static assets" do |app|
|
101
105
|
if app.config.view_component.show_previews
|
102
|
-
app.middleware.
|
106
|
+
app.middleware.use(::ActionDispatch::Static, "#{root}/app/assets/vendor")
|
103
107
|
end
|
104
108
|
end
|
105
109
|
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
module PolymorphicSlots
|
5
|
+
# In older rails versions, using a concern isn't a good idea here because they appear to not work with
|
6
|
+
# Module#prepend and class methods.
|
7
|
+
def self.included(base)
|
8
|
+
base.singleton_class.prepend(ClassMethods)
|
9
|
+
base.include(InstanceMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def renders_one(slot_name, callable = nil)
|
14
|
+
return super unless callable.is_a?(Hash) && callable.key?(:types)
|
15
|
+
|
16
|
+
validate_singular_slot_name(slot_name)
|
17
|
+
register_polymorphic_slot(slot_name, callable[:types], collection: false)
|
18
|
+
end
|
19
|
+
|
20
|
+
def renders_many(slot_name, callable = nil)
|
21
|
+
return super unless callable.is_a?(Hash) && callable.key?(:types)
|
22
|
+
|
23
|
+
validate_plural_slot_name(slot_name)
|
24
|
+
register_polymorphic_slot(slot_name, callable[:types], collection: true)
|
25
|
+
end
|
26
|
+
|
27
|
+
def register_polymorphic_slot(slot_name, types, collection:)
|
28
|
+
renderable_hash = types.each_with_object({}) do |(poly_type, poly_callable), memo|
|
29
|
+
memo[poly_type] = define_slot(
|
30
|
+
"#{slot_name}_#{poly_type}", collection: collection, callable: poly_callable
|
31
|
+
)
|
32
|
+
|
33
|
+
getter_name = slot_name
|
34
|
+
setter_name =
|
35
|
+
if collection
|
36
|
+
"#{ActiveSupport::Inflector.singularize(slot_name)}_#{poly_type}"
|
37
|
+
else
|
38
|
+
"#{slot_name}_#{poly_type}"
|
39
|
+
end
|
40
|
+
|
41
|
+
define_method(getter_name) do
|
42
|
+
get_slot(slot_name)
|
43
|
+
end
|
44
|
+
ruby2_keywords(getter_name.to_sym) if respond_to?(:ruby2_keywords, true)
|
45
|
+
|
46
|
+
define_method(setter_name) do |*args, &block|
|
47
|
+
set_polymorphic_slot(slot_name, poly_type, *args, &block)
|
48
|
+
end
|
49
|
+
ruby2_keywords(setter_name.to_sym) if respond_to?(:ruby2_keywords, true)
|
50
|
+
end
|
51
|
+
|
52
|
+
self.registered_slots[slot_name] = {
|
53
|
+
collection: collection,
|
54
|
+
renderable_hash: renderable_hash
|
55
|
+
}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
module InstanceMethods
|
60
|
+
def set_polymorphic_slot(slot_name, poly_type = nil, *args, &block)
|
61
|
+
slot_definition = self.class.registered_slots[slot_name]
|
62
|
+
|
63
|
+
if !slot_definition[:collection] && get_slot(slot_name)
|
64
|
+
raise ArgumentError, "content for slot '#{slot_name}' has already been provided"
|
65
|
+
end
|
66
|
+
|
67
|
+
poly_def = slot_definition[:renderable_hash][poly_type]
|
68
|
+
|
69
|
+
set_slot(slot_name, poly_def, *args, &block)
|
70
|
+
end
|
71
|
+
ruby2_keywords(:set_polymorphic_slot) if respond_to?(:ruby2_keywords, true)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
task stats: "view_component:statsetup"
|
4
|
+
|
5
|
+
namespace :view_component do
|
6
|
+
task statsetup: :environment do
|
7
|
+
require "rails/code_statistics"
|
8
|
+
|
9
|
+
::STATS_DIRECTORIES << ["ViewComponents", ViewComponent::Base.view_component_path]
|
10
|
+
end
|
11
|
+
end
|
@@ -62,9 +62,9 @@ module ViewComponent
|
|
62
62
|
view_context.capture(&@__vc_content_block)
|
63
63
|
elsif defined?(@__vc_content_set_by_with_content)
|
64
64
|
@__vc_content_set_by_with_content
|
65
|
-
|
65
|
+
end
|
66
66
|
|
67
|
-
@content
|
67
|
+
@content = @content.to_s
|
68
68
|
end
|
69
69
|
|
70
70
|
# Allow access to public component methods via the wrapper
|
@@ -89,6 +89,7 @@ module ViewComponent
|
|
89
89
|
def method_missing(symbol, *args, &block)
|
90
90
|
@__vc_component_instance.public_send(symbol, *args, &block)
|
91
91
|
end
|
92
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
92
93
|
|
93
94
|
def html_safe?
|
94
95
|
to_s.html_safe?
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support/concern"
|
4
|
-
|
5
4
|
require "view_component/slot_v2"
|
6
5
|
|
7
6
|
module ViewComponent
|
@@ -67,13 +66,14 @@ module ViewComponent
|
|
67
66
|
def renders_one(slot_name, callable = nil)
|
68
67
|
validate_singular_slot_name(slot_name)
|
69
68
|
|
70
|
-
define_method slot_name do |*args,
|
71
|
-
if args.empty? &&
|
69
|
+
define_method slot_name do |*args, &block|
|
70
|
+
if args.empty? && block.nil?
|
72
71
|
get_slot(slot_name)
|
73
72
|
else
|
74
|
-
set_slot(slot_name, *args,
|
73
|
+
set_slot(slot_name, nil, *args, &block)
|
75
74
|
end
|
76
75
|
end
|
76
|
+
ruby2_keywords(slot_name.to_sym) if respond_to?(:ruby2_keywords, true)
|
77
77
|
|
78
78
|
register_slot(slot_name, collection: false, callable: callable)
|
79
79
|
end
|
@@ -123,9 +123,10 @@ module ViewComponent
|
|
123
123
|
# Define setter for singular names
|
124
124
|
# e.g. `renders_many :items` allows fetching all tabs with
|
125
125
|
# `component.tabs` and setting a tab with `component.tab`
|
126
|
-
define_method singular_name do |*args,
|
127
|
-
set_slot(slot_name, *args,
|
126
|
+
define_method singular_name do |*args, &block|
|
127
|
+
set_slot(slot_name, nil, *args, &block)
|
128
128
|
end
|
129
|
+
ruby2_keywords(singular_name.to_sym) if respond_to?(:ruby2_keywords, true)
|
129
130
|
|
130
131
|
# Instantiates and and adds multiple slots forwarding the first
|
131
132
|
# argument to each slot constructor
|
@@ -134,7 +135,7 @@ module ViewComponent
|
|
134
135
|
get_slot(slot_name)
|
135
136
|
else
|
136
137
|
collection_args.map do |args|
|
137
|
-
set_slot(slot_name, **args, &block)
|
138
|
+
set_slot(slot_name, nil, **args, &block)
|
138
139
|
end
|
139
140
|
end
|
140
141
|
end
|
@@ -162,27 +163,37 @@ module ViewComponent
|
|
162
163
|
|
163
164
|
private
|
164
165
|
|
165
|
-
def register_slot(slot_name,
|
166
|
+
def register_slot(slot_name, **kwargs)
|
167
|
+
self.registered_slots[slot_name] = define_slot(slot_name, **kwargs)
|
168
|
+
end
|
169
|
+
|
170
|
+
def define_slot(slot_name, collection:, callable:)
|
166
171
|
# Setup basic slot data
|
167
172
|
slot = {
|
168
173
|
collection: collection,
|
169
174
|
}
|
175
|
+
return slot unless callable
|
176
|
+
|
170
177
|
# If callable responds to `render_in`, we set it on the slot as a renderable
|
171
|
-
if callable
|
178
|
+
if callable.respond_to?(:method_defined?) && callable.method_defined?(:render_in)
|
172
179
|
slot[:renderable] = callable
|
173
180
|
elsif callable.is_a?(String)
|
174
181
|
# If callable is a string, we assume it's referencing an internal class
|
175
182
|
slot[:renderable_class_name] = callable
|
176
|
-
elsif callable
|
183
|
+
elsif callable.respond_to?(:call)
|
177
184
|
# If slot does not respond to `render_in`, we assume it's a proc,
|
178
185
|
# define a method, and save a reference to it to call when setting
|
179
186
|
method_name = :"_call_#{slot_name}"
|
180
187
|
define_method method_name, &callable
|
181
188
|
slot[:renderable_function] = instance_method(method_name)
|
189
|
+
else
|
190
|
+
raise(
|
191
|
+
ArgumentError,
|
192
|
+
"invalid slot definition. Please pass a class, string, or callable (i.e. proc, lambda, etc)"
|
193
|
+
)
|
182
194
|
end
|
183
195
|
|
184
|
-
|
185
|
-
self.registered_slots[slot_name] = slot
|
196
|
+
slot
|
186
197
|
end
|
187
198
|
|
188
199
|
def validate_plural_slot_name(slot_name)
|
@@ -235,9 +246,8 @@ module ViewComponent
|
|
235
246
|
end
|
236
247
|
end
|
237
248
|
|
238
|
-
def set_slot(slot_name, *args,
|
239
|
-
slot_definition
|
240
|
-
|
249
|
+
def set_slot(slot_name, slot_definition = nil, *args, &block)
|
250
|
+
slot_definition ||= self.class.registered_slots[slot_name]
|
241
251
|
slot = SlotV2.new(self)
|
242
252
|
|
243
253
|
# Passing the block to the sub-component wrapper like this has two
|
@@ -253,23 +263,24 @@ module ViewComponent
|
|
253
263
|
|
254
264
|
# If class
|
255
265
|
if slot_definition[:renderable]
|
256
|
-
slot.__vc_component_instance = slot_definition[:renderable].new(*args
|
266
|
+
slot.__vc_component_instance = slot_definition[:renderable].new(*args)
|
257
267
|
# If class name as a string
|
258
268
|
elsif slot_definition[:renderable_class_name]
|
259
269
|
slot.__vc_component_instance =
|
260
|
-
self.class.const_get(slot_definition[:renderable_class_name]).new(*args
|
270
|
+
self.class.const_get(slot_definition[:renderable_class_name]).new(*args)
|
261
271
|
# If passed a lambda
|
262
272
|
elsif slot_definition[:renderable_function]
|
263
273
|
# Use `bind(self)` to ensure lambda is executed in the context of the
|
264
274
|
# current component. This is necessary to allow the lambda to access helper
|
265
275
|
# methods like `content_tag` as well as parent component state.
|
276
|
+
renderable_function = slot_definition[:renderable_function].bind(self)
|
266
277
|
renderable_value =
|
267
278
|
if block_given?
|
268
|
-
|
269
|
-
view_context.capture(*args,
|
279
|
+
renderable_function.call(*args) do |*args|
|
280
|
+
view_context.capture(*args, &block)
|
270
281
|
end
|
271
282
|
else
|
272
|
-
|
283
|
+
renderable_function.call(*args)
|
273
284
|
end
|
274
285
|
|
275
286
|
# Function calls can return components, so if it's a component handle it specially
|
@@ -291,5 +302,6 @@ module ViewComponent
|
|
291
302
|
|
292
303
|
slot
|
293
304
|
end
|
305
|
+
ruby2_keywords(:set_slot) if respond_to?(:ruby2_keywords, true)
|
294
306
|
end
|
295
307
|
end
|
@@ -27,8 +27,19 @@ module ViewComponent
|
|
27
27
|
# :nocov:
|
28
28
|
end
|
29
29
|
|
30
|
+
# @private
|
30
31
|
attr_reader :rendered_component
|
31
32
|
|
33
|
+
# Render a component inline. Internally sets `page` to be a `Capybara::Node::Simple`,
|
34
|
+
# allowing for Capybara assertions to be used:
|
35
|
+
#
|
36
|
+
# ```ruby
|
37
|
+
# render_inline(MyComponent.new)
|
38
|
+
# assert_text("Hello, World!")
|
39
|
+
# ```
|
40
|
+
#
|
41
|
+
# @param component [ViewComponent::Base] The instance of the component to be rendered.
|
42
|
+
# @return [Nokogiri::HTML]
|
32
43
|
def render_inline(component, **args, &block)
|
33
44
|
@rendered_component =
|
34
45
|
if Rails.version.to_f >= 6.1
|
@@ -40,10 +51,12 @@ module ViewComponent
|
|
40
51
|
Nokogiri::HTML.fragment(@rendered_component)
|
41
52
|
end
|
42
53
|
|
54
|
+
# @private
|
43
55
|
def controller
|
44
56
|
@controller ||= build_controller(Base.test_controller.constantize)
|
45
57
|
end
|
46
58
|
|
59
|
+
# @private
|
47
60
|
def request
|
48
61
|
@request ||=
|
49
62
|
begin
|
@@ -53,6 +66,15 @@ module ViewComponent
|
|
53
66
|
end
|
54
67
|
end
|
55
68
|
|
69
|
+
# Set the Action Pack request variant for the given block:
|
70
|
+
#
|
71
|
+
# ```ruby
|
72
|
+
# with_variant(:phone) do
|
73
|
+
# render_inline(MyComponent.new)
|
74
|
+
# end
|
75
|
+
# ```
|
76
|
+
#
|
77
|
+
# @param variant [Symbol] The variant to be set for the provided block.
|
56
78
|
def with_variant(variant)
|
57
79
|
old_variants = controller.view_context.lookup_context.variants
|
58
80
|
|
@@ -62,6 +84,16 @@ module ViewComponent
|
|
62
84
|
controller.view_context.lookup_context.variants = old_variants
|
63
85
|
end
|
64
86
|
|
87
|
+
# Set the controller to be used while executing the given block,
|
88
|
+
# allowing access to controller-specific methods:
|
89
|
+
#
|
90
|
+
# ```ruby
|
91
|
+
# with_controller_class(UsersController) do
|
92
|
+
# render_inline(MyComponent.new)
|
93
|
+
# end
|
94
|
+
# ```
|
95
|
+
#
|
96
|
+
# @param klass [ActionController::Base] The controller to be used.
|
65
97
|
def with_controller_class(klass)
|
66
98
|
old_controller = defined?(@controller) && @controller
|
67
99
|
|
@@ -71,17 +103,30 @@ module ViewComponent
|
|
71
103
|
@controller = old_controller
|
72
104
|
end
|
73
105
|
|
106
|
+
# Set the URL for the current request (such as when using request-dependent path helpers):
|
107
|
+
#
|
108
|
+
# ```ruby
|
109
|
+
# with_request_url("/users/42") do
|
110
|
+
# render_inline(MyComponent.new)
|
111
|
+
# end
|
112
|
+
# ```
|
113
|
+
#
|
114
|
+
# @param path [String] The path to set for the current request.
|
74
115
|
def with_request_url(path)
|
75
116
|
old_request_path_parameters = request.path_parameters
|
117
|
+
old_request_query_parameters = request.query_parameters
|
76
118
|
old_controller = defined?(@controller) && @controller
|
77
119
|
|
78
120
|
request.path_parameters = Rails.application.routes.recognize_path(path)
|
121
|
+
request.set_header("action_dispatch.request.query_parameters", Rack::Utils.parse_query(path.split("?")[1]))
|
79
122
|
yield
|
80
123
|
ensure
|
81
124
|
request.path_parameters = old_request_path_parameters
|
125
|
+
request.set_header("action_dispatch.request.query_parameters", old_request_query_parameters)
|
82
126
|
@controller = old_controller
|
83
127
|
end
|
84
128
|
|
129
|
+
# @private
|
85
130
|
def build_controller(klass)
|
86
131
|
klass.new.tap { |c| c.request = request }.extend(Rails.application.routes.url_helpers)
|
87
132
|
end
|
data/lib/view_component.rb
CHANGED
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.43.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-11-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -226,6 +226,20 @@ dependencies:
|
|
226
226
|
- - "~>"
|
227
227
|
- !ruby/object:Gem::Version
|
228
228
|
version: '4.0'
|
229
|
+
- !ruby/object:Gem::Dependency
|
230
|
+
name: sprockets-rails
|
231
|
+
requirement: !ruby/object:Gem::Requirement
|
232
|
+
requirements:
|
233
|
+
- - "~>"
|
234
|
+
- !ruby/object:Gem::Version
|
235
|
+
version: 3.2.2
|
236
|
+
type: :development
|
237
|
+
prerelease: false
|
238
|
+
version_requirements: !ruby/object:Gem::Requirement
|
239
|
+
requirements:
|
240
|
+
- - "~>"
|
241
|
+
- !ruby/object:Gem::Version
|
242
|
+
version: 3.2.2
|
229
243
|
- !ruby/object:Gem::Dependency
|
230
244
|
name: yard
|
231
245
|
requirement: !ruby/object:Gem::Requirement
|
@@ -300,9 +314,11 @@ files:
|
|
300
314
|
- lib/view_component/content_areas.rb
|
301
315
|
- lib/view_component/engine.rb
|
302
316
|
- lib/view_component/instrumentation.rb
|
317
|
+
- lib/view_component/polymorphic_slots.rb
|
303
318
|
- lib/view_component/preview.rb
|
304
319
|
- lib/view_component/preview_template_error.rb
|
305
320
|
- lib/view_component/previewable.rb
|
321
|
+
- lib/view_component/rails/tasks/view_component.rake
|
306
322
|
- lib/view_component/render_component_helper.rb
|
307
323
|
- lib/view_component/render_component_to_string_helper.rb
|
308
324
|
- lib/view_component/render_monkey_patch.rb
|