view_component 2.39.0 → 2.43.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.

Potentially problematic release.


This version of view_component might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e5f28b38a232a62dbe80033fe0ac5a7b0ee717f933afc76a1e991396ff51a8d6
4
- data.tar.gz: ed6610e4fc417a876aeac464439cc627c5ce6e242a65faa0d7db001e2249035a
3
+ metadata.gz: 3213b43fb64df68370b007c5e90029923c1b35d5893bfb1c57fad904d2df99fc
4
+ data.tar.gz: d13ad45c178645ba3c639cd889ec9f645defe96579f0a94d305b0ce940405bb1
5
5
  SHA512:
6
- metadata.gz: d7e130233a0699c54a1a9664c9a893afc6df8e45795d397ce6592e2edbd719c61ed6ef9fb373b3dc8d8285bc3a2d712c013579303138fb466806e646c2480465
7
- data.tar.gz: 25001a6bff37068aceccfb029d5829cd759f4662f7f0b8eb4ab76a42bf0a31ba55f1ceb97480c4a285dc3e9d9da62e746194f623f6b443c647fdb17bb21b3963
6
+ metadata.gz: 159ff39bb666465d3ae385b9abed08c977c748d4c4c2d2112c1dd87b7c2823a819d253af78a9b14ead1519bbd15c79de4c92518266ebe2fd88fd9de4850bf887
7
+ data.tar.gz: 7a62698b2449485ac139b89706c785f1466b49e98551df4a70897a347f8cc64619a072b860ebdd09869728978699e7926e3a09649abe083b954d4e18c746cc18
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # ViewComponent
1
+ <img src="/docs/logo/viewcomponent-color-logo.svg" alt="ViewComponent logo" width="500">
2
2
 
3
3
  A framework for building reusable, testable & encapsulated view components in Ruby on Rails.
4
4
 
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
- *David Backeus*
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
- defined?(ApplicationComponent) ? "ApplicationComponent" : "ViewComponent::Base"
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
@@ -1,4 +1,4 @@
1
- import { Controller } from "stimulus";
1
+ import { Controller } from "<%= stimulus_module %>";
2
2
 
3
3
  export default class extends Controller {
4
4
  connect() {
@@ -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: ActionView context from calling view
43
- # block: optional block to be captured within the view context
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
- # app/components/my_component.html.erb
57
- # <span title="<%= @title %>">Hello, <%= content %>!</span>
48
+ # Returns HTML that has been escaped by the respective template handler.
58
49
  #
59
- # In use:
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
- view_context.render(options, args, &block)
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
- @__vc_helpers ||= controller.view_context
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.insert_before(::ActionDispatch::Static, ::ActionDispatch::Static, "#{root}/app/assets/vendor")
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
@@ -5,6 +5,7 @@ require "active_support/descendants_tracker"
5
5
  module ViewComponent # :nodoc:
6
6
  class Preview
7
7
  include ActionView::Helpers::TagHelper
8
+ include ActionView::Helpers::AssetTagHelper
8
9
  extend ActiveSupport::DescendantsTracker
9
10
 
10
11
  def render(component, **args, &block)
@@ -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
- end
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, **kwargs, &block|
71
- if args.empty? && kwargs.empty? && block.nil?
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, **kwargs, &block)
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, **kwargs, &block|
127
- set_slot(slot_name, *args, **kwargs, &block)
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, collection:, callable:)
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 && callable.respond_to?(:method_defined?) && callable.method_defined?(:render_in)
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
- # Register the slot on the component
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, **kwargs, &block)
239
- slot_definition = self.class.registered_slots[slot_name]
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, **kwargs)
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, **kwargs)
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
- slot_definition[:renderable_function].bind(self).call(*args, **kwargs) do |*args, **kwargs|
269
- view_context.capture(*args, **kwargs, &block)
279
+ renderable_function.call(*args) do |*args|
280
+ view_context.capture(*args, &block)
270
281
  end
271
282
  else
272
- slot_definition[:renderable_function].bind(self).call(*args, **kwargs)
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
@@ -3,7 +3,7 @@
3
3
  module ViewComponent
4
4
  module VERSION
5
5
  MAJOR = 2
6
- MINOR = 39
6
+ MINOR = 43
7
7
  PATCH = 0
8
8
 
9
9
  STRING = [MAJOR, MINOR, PATCH].join(".")
@@ -17,3 +17,5 @@ module ViewComponent
17
17
  autoload :TemplateError
18
18
  autoload :Translatable
19
19
  end
20
+
21
+ require "view_component/engine" if defined?(Rails::Engine)
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.39.0
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-08-31 00:00:00.000000000 Z
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