view_component 2.82.0 → 3.0.0.rc2

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 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