view_component 2.82.0 → 3.0.0.rc2

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: 9bb20706245c43b9c5fa4d11087a2e98332bda53d659f7065874a3c0b8545547
4
- data.tar.gz: 13821a1e91da8a8dce8270962b99aa3b84fddb9731f99c2e2acfd143ad017836
3
+ metadata.gz: f2171ee963d59844894858309db964f092fb423bc4828a3bccbc917608502278
4
+ data.tar.gz: aaf7c734530bf46ea0fa5e7fdad4c6efaf7325eb45970fa2bea56d6c9f9a15c1
5
5
  SHA512:
6
- metadata.gz: 5b0ebf7ac54fc2c82374b29f9cceadfe55af73516ad2e4c65dad69cd65a7c63de3e00ef9cdba8b359c1061309f9a0d2f90344374feec2b37211fbfb36282c96b
7
- data.tar.gz: 48e156dcc5fa1ac9a6cff64e4cb585d528103452938a515d1e3598b505b03188e89362c393c86c0a05437f9a8c28f22e937e3d706f3da17ae03a7c1371728505
6
+ metadata.gz: 5f26a04b1af1b93be44db4436d57544c182c64a1d1036a2db62cecf0bc8b12da3f7ccf11e7cb8bf88c9c915495686ddcd774cca13a5adf7353dfc678d6c7ead1
7
+ data.tar.gz: a982b51ed80dc7da05da996005b2e60ca7af576a91c5ef13cdd68698735635fb42834e7cc45d23e5cbf45326b4ab8d8b85a1e6ddb7b2967a6d1e45638c73d50a
data/docs/CHANGELOG.md CHANGED
@@ -10,6 +10,108 @@ nav_order: 5
10
10
 
11
11
  ## main
12
12
 
13
+ ## v3.0.0.rc2
14
+
15
+ Run into an issue with this release? [Let us know](https://github.com/ViewComponent/view_component/issues/1629).
16
+
17
+ * BREAKING: Rename `SlotV2` to `Slot` and `SlotableV2` to `Slotable`.
18
+
19
+ *Joel Hawksley*
20
+
21
+ * BREAKING: Incorporate `PolymorphicSlots` into `Slotable`. To migrate, remove any references to `PolymorphicSlots` as they are no longer necessary.
22
+
23
+ *Joel Hawksley*
24
+
25
+ * BREAKING: Rename private TestHelpers#controller, #build_controller, #request, and #preview_class to avoid conflicts. Note: While these methods were undocumented and marked as private, they was easily accessible in tests. As such, we're cautiously considering this to be a breaking change.
26
+
27
+ *Joel Hawksley*
28
+
29
+ * Avoid loading ActionView::Base during Rails initialization. Originally submitted in #1528.
30
+
31
+ *Jonathan del Strother*
32
+
33
+ * Improve documentation of known incompatibilities with Rails form helpers.
34
+
35
+ *Tobias L. Maier*
36
+
37
+ * Remove dependency on environment task from `view_component:statsetup`.
38
+
39
+ *Svetlin Simonyan*
40
+
41
+ * Add experimental `config.view_component.capture_compatibility_patch_enabled` option resolving rendering issues related to forms, capture, turbo frames, etc.
42
+
43
+ *Blake Williams*
44
+
45
+ * Add `#content?` method that indicates if content has been passed to component.
46
+
47
+ *Joel Hawksley*
48
+
49
+ * Added example of a custom preview controller.
50
+
51
+ *Graham Rogers*
52
+
53
+ * Add Krystal to list of companies using ViewComponent.
54
+
55
+ *Matt Bearman*
56
+
57
+ * Add Mon Ami to list of companies using ViewComponent.
58
+
59
+ *Ethan Lee-Tyson*
60
+
61
+ ## 3.0.0.rc1
62
+
63
+ 1,000+ days and 100+ releases later, the 200+ contributors to ViewComponent are proud to ship v3.0.0!
64
+
65
+ We're so grateful for all of the work of community members to get us to this release. Whether it’s filing bug reports, designing APIs in long-winded discussion threads, or writing code itself, ViewComponent is built by the community, for the community. We couldn’t be more proud of what we’re building together :heart:
66
+
67
+ This release makes the following breaking changes, many of which have long been deprecated:
68
+
69
+ * BREAKING: Remove deprecated slots setter methods. Use `with_SLOT_NAME` instead.
70
+
71
+ *Joel Hawksley*
72
+
73
+ * BREAKING: Remove deprecated SlotsV1 in favor of current SlotsV2.
74
+
75
+ *Joel Hawksley*
76
+
77
+ * BREAKING: Remove deprecated `content_areas` feature. Use Slots instead.
78
+
79
+ *Joel Hawksley*
80
+
81
+ * BREAKING: Remove deprecated support for loading ViewComponent engine manually. Make sure `require "view_component/engine"` is removed from `Gemfile`.
82
+
83
+ *Joel Hawksley*
84
+
85
+ * BREAKING: Remove deprecated `generate_*` methods. Use `generate.*` instead.
86
+
87
+ *Joel Hawksley*
88
+
89
+ * BREAKING: Remove deprecated `with_variant` method.
90
+
91
+ *Joel Hawksley*
92
+
93
+ * BREAKING: Remove deprecated `rendered_component` in favor of `rendered_content`.
94
+
95
+ *Joel Hawksley*
96
+
97
+ * BREAKING: Remove deprecated `config.preview_path` in favor of `config.preview_paths`.
98
+
99
+ *Joel Hawksley*
100
+
101
+ * BREAKING: Support Ruby 2.7+ instead of 2.4+
102
+
103
+ *Joel Hawksley*
104
+
105
+ * BREAKING: Remove deprecated `before_render_check`.
106
+
107
+ *Joel Hawksley*
108
+
109
+ * BREAKING: Change counter variable to start iterating from `0` instead of `1`.
110
+
111
+ *Frank S*
112
+
113
+ Run into an issue with this release? [Let us know](https://github.com/ViewComponent/view_component/issues/1629).
114
+
13
115
  ## 2.82.0
14
116
 
15
117
  * Revert "Avoid loading ActionView::Base during initialization (#1528)"
@@ -6,11 +6,8 @@ require "view_component/collection"
6
6
  require "view_component/compile_cache"
7
7
  require "view_component/compiler"
8
8
  require "view_component/config"
9
- require "view_component/content_areas"
10
- require "view_component/polymorphic_slots"
11
9
  require "view_component/preview"
12
10
  require "view_component/slotable"
13
- require "view_component/slotable_v2"
14
11
  require "view_component/translatable"
15
12
  require "view_component/with_content_helper"
16
13
 
@@ -23,7 +20,7 @@ module ViewComponent
23
20
  #
24
21
  # @return [ViewComponent::Config]
25
22
  def config
26
- @config ||= ViewComponent::Config.defaults
23
+ @config ||= ActiveSupport::OrderedOptions.new
27
24
  end
28
25
 
29
26
  # Replaces the entire config. You shouldn't need to use this directly
@@ -31,9 +28,7 @@ module ViewComponent
31
28
  attr_writer :config
32
29
  end
33
30
 
34
- include ViewComponent::ContentAreas
35
- include ViewComponent::PolymorphicSlots
36
- include ViewComponent::SlotableV2
31
+ include ViewComponent::Slotable
37
32
  include ViewComponent::Translatable
38
33
  include ViewComponent::WithContentHelper
39
34
 
@@ -44,9 +39,6 @@ module ViewComponent
44
39
  # For CSRF authenticity tokens in forms
45
40
  delegate :form_authenticity_token, :protect_against_forgery?, :config, to: :helpers
46
41
 
47
- class_attribute :content_areas
48
- self.content_areas = [] # class_attribute:default doesn't work until Rails 5.2
49
-
50
42
  # Config option that strips trailing whitespace in templates before compiling them.
51
43
  class_attribute :__vc_strip_trailing_whitespace, instance_accessor: false, instance_predicate: false
52
44
  self.__vc_strip_trailing_whitespace = false # class_attribute:default doesn't work until Rails 5.2
@@ -66,23 +58,6 @@ module ViewComponent
66
58
  self.__vc_original_view_context = view_context
67
59
  end
68
60
 
69
- # @!macro [attach] deprecated_generate_mattr_accessor
70
- # @method generate_$1
71
- # @deprecated Use `#generate.$1` instead. Will be removed in v3.0.0.
72
- def self._deprecated_generate_mattr_accessor(name)
73
- define_singleton_method("generate_#{name}".to_sym) do
74
- generate.public_send(name)
75
- end
76
- define_singleton_method("generate_#{name}=".to_sym) do |value|
77
- generate.public_send("#{name}=".to_sym, value)
78
- end
79
- end
80
-
81
- _deprecated_generate_mattr_accessor :distinct_locale_files
82
- _deprecated_generate_mattr_accessor :locale
83
- _deprecated_generate_mattr_accessor :sidecar
84
- _deprecated_generate_mattr_accessor :stimulus_controller
85
-
86
61
  # Entrypoint for rendering components.
87
62
  #
88
63
  # - `view_context`: ActionView context from calling view
@@ -165,14 +140,6 @@ module ViewComponent
165
140
  #
166
141
  # @return [void]
167
142
  def before_render
168
- before_render_check
169
- end
170
-
171
- # Called after rendering the component.
172
- #
173
- # @deprecated Use `#before_render` instead. Will be removed in v3.0.0.
174
- # @return [void]
175
- def before_render_check
176
143
  # noop
177
144
  end
178
145
 
@@ -265,22 +232,9 @@ module ViewComponent
265
232
  #
266
233
  # @private
267
234
  def format
268
- # Ruby 2.6 throws a warning without checking `defined?`, 2.7 doesn't
269
235
  @__vc_variant if defined?(@__vc_variant)
270
236
  end
271
237
 
272
- # Use the provided variant instead of the one determined by the current request.
273
- #
274
- # @deprecated Will be removed in v3.0.0.
275
- # @param variant [Symbol] The variant to be used by the component.
276
- # @return [self]
277
- def with_variant(variant)
278
- @__vc_variant = variant
279
-
280
- self
281
- end
282
- deprecate :with_variant, deprecator: ViewComponent::Deprecation
283
-
284
238
  # The current request. Use sparingly as doing so introduces coupling that
285
239
  # inhibits encapsulation & reuse, often making testing difficult.
286
240
  #
@@ -289,22 +243,40 @@ module ViewComponent
289
243
  @request ||= controller.request if controller.respond_to?(:request)
290
244
  end
291
245
 
292
- private
293
-
294
- attr_reader :view_context
295
-
246
+ # The content passed to the component instance as a block.
247
+ #
248
+ # @return [String]
296
249
  def content
297
250
  @__vc_content_evaluated = true
298
251
  return @__vc_content if defined?(@__vc_content)
299
252
 
300
253
  @__vc_content =
301
- if @view_context && @__vc_render_in_block
254
+ if __vc_render_in_block_provided?
302
255
  view_context.capture(self, &@__vc_render_in_block)
303
- elsif defined?(@__vc_content_set_by_with_content)
256
+ elsif __vc_content_set_by_with_content_defined?
304
257
  @__vc_content_set_by_with_content
305
258
  end
306
259
  end
307
260
 
261
+ # Whether `content` has been passed to the component.
262
+ #
263
+ # @return [Boolean]
264
+ def content?
265
+ __vc_render_in_block_provided? || __vc_content_set_by_with_content_defined?
266
+ end
267
+
268
+ private
269
+
270
+ attr_reader :view_context
271
+
272
+ def __vc_render_in_block_provided?
273
+ @view_context && @__vc_render_in_block
274
+ end
275
+
276
+ def __vc_content_set_by_with_content_defined?
277
+ defined?(@__vc_content_set_by_with_content)
278
+ end
279
+
308
280
  def content_evaluated?
309
281
  @__vc_content_evaluated
310
282
  end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ # CaptureCompatibility is a module that patches #capture to fix issues
5
+ # related to ViewComponent and functionality that relies on `capture`
6
+ # like forms, capture itself, turbo frames, etc.
7
+ #
8
+ # This underlying incompatibility with ViewComponent and capture is
9
+ # that several features like forms keep a reference to the primary
10
+ # `ActionView::Base` instance which has its own @output_buffer. When
11
+ # `#capture` is called on the original `ActionView::Base` instance while
12
+ # evaluating a block from a ViewComponent the @output_buffer is overridden
13
+ # in the ActionView::Base instance, and *not* the component. This results
14
+ # in a double render due to `#capture` implementation details.
15
+ #
16
+ # To resolve the issue, we override `#capture` so that we can delegate
17
+ # the `capture` logic to the ViewComponent that created the block.
18
+ module CaptureCompatibility
19
+ def self.included(base)
20
+ base.class_eval do
21
+ alias_method :original_capture, :capture
22
+ end
23
+
24
+ base.prepend(InstanceMethods)
25
+ end
26
+
27
+ module InstanceMethods
28
+ def capture(*args, &block)
29
+ # Handle blocks that originate from C code and raise, such as `&:method`
30
+ return original_capture(*args, &block) if block.source_location.nil?
31
+
32
+ block_context = block.binding.receiver
33
+
34
+ if block_context != self && block_context.class < ActionView::Base
35
+ block_context.original_capture(*args, &block)
36
+ else
37
+ original_capture(*args, &block)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -62,7 +62,7 @@ module ViewComponent
62
62
 
63
63
  def component_options(item, iterator)
64
64
  item_options = {component.collection_parameter => item}
65
- item_options[component.collection_counter_parameter] = iterator.index + 1 if component.counter_argument_present?
65
+ item_options[component.collection_counter_parameter] = iterator.index if component.counter_argument_present?
66
66
  item_options[component.collection_iteration_parameter] = iterator.dup if component.iteration_argument_present?
67
67
 
68
68
  @options.merge(item_options)
@@ -46,12 +46,6 @@ module ViewComponent
46
46
  return false
47
47
  end
48
48
 
49
- if subclass_instance_methods.include?(:before_render_check)
50
- ViewComponent::Deprecation.deprecation_warning(
51
- "`before_render_check`", :"`before_render`"
52
- )
53
- end
54
-
55
49
  if raise_errors
56
50
  component_class.validate_initialization_parameters!
57
51
  component_class.validate_collection_parameter!
@@ -23,7 +23,8 @@ module ViewComponent
23
23
  show_previews: Rails.env.development? || Rails.env.test?,
24
24
  preview_paths: default_preview_paths,
25
25
  test_controller: "ApplicationController",
26
- default_preview_layout: nil
26
+ default_preview_layout: nil,
27
+ capture_compatibility_patch_enabled: false
27
28
  })
28
29
  end
29
30
 
@@ -126,9 +127,6 @@ module ViewComponent
126
127
  # The locations in which component previews will be looked up.
127
128
  # Defaults to `['test/component/previews']` relative to your Rails root.
128
129
 
129
- # @!attribute preview_path
130
- # @deprecated Use #preview_paths instead. Will be removed in v3.0.0.
131
-
132
130
  # @!attribute test_controller
133
131
  # @return [String]
134
132
  # The controller used for testing components.
@@ -140,6 +138,13 @@ module ViewComponent
140
138
  # A custom default layout used for the previews index page and individual
141
139
  # previews.
142
140
  # Defaults to `nil`. If this is falsy, `"component_preview"` is used.
141
+ #
142
+ # @!attribute capture_compatibility_patch_enabled
143
+ # @return [Boolean]
144
+ # Enables the experimental capture compatibility patch that makes ViewComponent
145
+ # compatible with forms, capture, and other built-ins.
146
+ # previews.
147
+ # Defaults to `false`.
143
148
 
144
149
  def default_preview_paths
145
150
  return [] unless defined?(Rails.root) && Dir.exist?("#{Rails.root}/test/components/previews")
@@ -158,15 +163,6 @@ module ViewComponent
158
163
  @config = self.class.defaults
159
164
  end
160
165
 
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
166
  delegate_missing_to :config
171
167
 
172
168
  private
@@ -3,6 +3,6 @@
3
3
  require "active_support/deprecation"
4
4
 
5
5
  module ViewComponent
6
- DEPRECATION_HORIZON = "3.0.0"
6
+ DEPRECATION_HORIZON = "4.0.0"
7
7
  Deprecation = ActiveSupport::Deprecation.new(DEPRECATION_HORIZON, "ViewComponent")
8
8
  end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rails"
4
- require "view_component/base"
4
+ require "view_component/config"
5
5
 
6
6
  module ViewComponent
7
7
  class Engine < Rails::Engine # :nodoc:
8
- config.view_component = ViewComponent::Base.config
8
+ config.view_component = ViewComponent::Config.defaults
9
9
 
10
10
  rake_tasks do
11
11
  load "view_component/rails/tasks/view_component.rake"
@@ -14,9 +14,6 @@ module ViewComponent
14
14
  initializer "view_component.set_configs" do |app|
15
15
  options = app.config.view_component
16
16
 
17
- %i[generate preview_controller preview_route show_previews_source].each do |config_option|
18
- options[config_option] ||= ViewComponent::Base.public_send(config_option)
19
- end
20
17
  options.instrumentation_enabled = false if options.instrumentation_enabled.nil?
21
18
  options.render_monkey_patch_enabled = true if options.render_monkey_patch_enabled.nil?
22
19
  options.show_previews = (Rails.env.development? || Rails.env.test?) if options.show_previews.nil?
@@ -39,6 +36,8 @@ module ViewComponent
39
36
 
40
37
  initializer "view_component.enable_instrumentation" do |app|
41
38
  ActiveSupport.on_load(:view_component) do
39
+ Base.config = app.config.view_component
40
+
42
41
  if app.config.view_component.instrumentation_enabled.present?
43
42
  # :nocov:
44
43
  ViewComponent::Base.prepend(ViewComponent::Instrumentation)
@@ -47,6 +46,14 @@ module ViewComponent
47
46
  end
48
47
  end
49
48
 
49
+ # :nocov:
50
+ initializer "view_component.enable_capture_patch" do |app|
51
+ ActiveSupport.on_load(:view_component) do
52
+ ActionView::Base.include(ViewComponent::CaptureCompatibility) if app.config.view_component.capture_compatibility_patch_enabled
53
+ end
54
+ end
55
+ # :nocov:
56
+
50
57
  initializer "view_component.set_autoload_paths" do |app|
51
58
  options = app.config.view_component
52
59
 
@@ -143,20 +150,3 @@ module ViewComponent
143
150
  end
144
151
  end
145
152
  end
146
-
147
- if RUBY_VERSION < "2.7.0"
148
- ViewComponent::Deprecation.deprecation_warning("Support for Ruby versions < 2.7.0")
149
- end
150
-
151
- # :nocov:
152
- unless defined?(ViewComponent::Base)
153
- require "view_component/deprecation"
154
-
155
- ViewComponent::Deprecation.deprecation_warning(
156
- "Manually loading the engine",
157
- "remove `require \"view_component/engine\"`"
158
- )
159
-
160
- require "view_component"
161
- end
162
- # :nocov:
@@ -3,7 +3,7 @@
3
3
  task stats: "view_component:statsetup"
4
4
 
5
5
  namespace :view_component do
6
- task statsetup: :environment do
6
+ task :statsetup do
7
7
  require "rails/code_statistics"
8
8
 
9
9
  ::STATS_DIRECTORIES << ["ViewComponents", ViewComponent::Base.view_component_path]
@@ -1,7 +1,98 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "view_component/with_content_helper"
4
+
3
5
  module ViewComponent
4
6
  class Slot
5
- attr_accessor :content
7
+ include ViewComponent::WithContentHelper
8
+
9
+ attr_writer :__vc_component_instance, :__vc_content_block, :__vc_content
10
+
11
+ def initialize(parent)
12
+ @parent = parent
13
+ end
14
+
15
+ # Used to render the slot content in the template
16
+ #
17
+ # There's currently 3 different values that may be set, that we can render.
18
+ #
19
+ # If the slot renderable is a component, the string class name of a
20
+ # component, or a function that returns a component, we render that
21
+ # component instance, returning the string.
22
+ #
23
+ # If the slot renderable is a function and returns a string, it's
24
+ # set as `@__vc_content` and is returned directly.
25
+ #
26
+ # If there is no slot renderable, we evaluate the block passed to
27
+ # the slot and return it.
28
+ def to_s
29
+ return @content if defined?(@content)
30
+
31
+ view_context = @parent.send(:view_context)
32
+
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
40
+
41
+ @content =
42
+ if defined?(@__vc_component_instance)
43
+ @__vc_component_instance.__vc_original_view_context = @parent.__vc_original_view_context
44
+
45
+ if defined?(@__vc_content_set_by_with_content)
46
+ @__vc_component_instance.with_content(@__vc_content_set_by_with_content)
47
+
48
+ @__vc_component_instance.render_in(view_context)
49
+ elsif defined?(@__vc_content_block)
50
+ # render_in is faster than `parent.render`
51
+ @__vc_component_instance.render_in(view_context, &@__vc_content_block)
52
+ else
53
+ @__vc_component_instance.render_in(view_context)
54
+ end
55
+ elsif defined?(@__vc_content)
56
+ @__vc_content
57
+ elsif defined?(@__vc_content_block)
58
+ view_context.capture(&@__vc_content_block)
59
+ elsif defined?(@__vc_content_set_by_with_content)
60
+ @__vc_content_set_by_with_content
61
+ end
62
+
63
+ @content = @content.to_s
64
+ end
65
+
66
+ # Allow access to public component methods via the wrapper
67
+ #
68
+ # for example
69
+ #
70
+ # calling `header.name` (where `header` is a slot) will call `name`
71
+ # on the `HeaderComponent` instance.
72
+ #
73
+ # Where the component may look like:
74
+ #
75
+ # class MyComponent < ViewComponent::Base
76
+ # has_one :header, HeaderComponent
77
+ #
78
+ # class HeaderComponent < ViewComponent::Base
79
+ # def name
80
+ # @name
81
+ # end
82
+ # end
83
+ # end
84
+ #
85
+ def method_missing(symbol, *args, &block)
86
+ @__vc_component_instance.public_send(symbol, *args, &block)
87
+ end
88
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
89
+
90
+ def html_safe?
91
+ to_s.html_safe?
92
+ end
93
+
94
+ def respond_to_missing?(symbol, include_all = false)
95
+ defined?(@__vc_component_instance) && @__vc_component_instance.respond_to?(symbol, include_all)
96
+ end
6
97
  end
7
98
  end