view_component 3.23.2 → 4.12.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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/view_component/preview_actions.rb +11 -14
  3. data/app/controllers/view_components_system_test_controller.rb +25 -19
  4. data/app/views/test_mailer/test_asset_email.html.erb +1 -0
  5. data/app/views/test_mailer/test_url_email.html.erb +1 -0
  6. data/app/views/view_components/preview.html.erb +1 -9
  7. data/docs/CHANGELOG.md +612 -108
  8. data/lib/{rails/generators → generators/view_component}/abstract_generator.rb +2 -2
  9. data/lib/{rails/generators → generators/view_component}/component/component_generator.rb +17 -4
  10. data/lib/{rails/generators → generators/view_component}/component/templates/component.rb.tt +6 -1
  11. data/lib/{rails/generators/erb/component_generator.rb → generators/view_component/erb/erb_generator.rb} +4 -3
  12. data/lib/{rails/generators/haml/component_generator.rb → generators/view_component/haml/haml_generator.rb} +3 -3
  13. data/lib/{rails/generators/locale/component_generator.rb → generators/view_component/locale/locale_generator.rb} +3 -3
  14. data/lib/{rails/generators/preview/component_generator.rb → generators/view_component/preview/preview_generator.rb} +3 -3
  15. data/lib/{rails/generators/rspec/component_generator.rb → generators/view_component/rspec/rspec_generator.rb} +3 -3
  16. data/lib/{rails/generators/slim/component_generator.rb → generators/view_component/slim/slim_generator.rb} +3 -3
  17. data/lib/{rails/generators/stimulus/component_generator.rb → generators/view_component/stimulus/stimulus_generator.rb} +3 -3
  18. data/lib/generators/view_component/tailwindcss/tailwindcss_generator.rb +11 -0
  19. data/lib/{rails/generators/test_unit/component_generator.rb → generators/view_component/test_unit/test_unit_generator.rb} +2 -2
  20. data/lib/view_component/base.rb +252 -163
  21. data/lib/view_component/collection.rb +33 -35
  22. data/lib/view_component/compiler.rb +55 -80
  23. data/lib/view_component/config.rb +55 -85
  24. data/lib/view_component/configurable.rb +19 -3
  25. data/lib/view_component/deprecation.rb +1 -1
  26. data/lib/view_component/engine.rb +46 -106
  27. data/lib/view_component/errors.rb +16 -34
  28. data/lib/view_component/inline_template.rb +4 -4
  29. data/lib/view_component/instrumentation.rb +21 -14
  30. data/lib/view_component/preview.rb +7 -12
  31. data/lib/view_component/request_details.rb +30 -0
  32. data/lib/view_component/slot.rb +7 -15
  33. data/lib/view_component/slotable.rb +94 -80
  34. data/lib/view_component/system_spec_helpers.rb +11 -0
  35. data/lib/view_component/system_test_helpers.rb +2 -2
  36. data/lib/view_component/template.rb +159 -83
  37. data/lib/view_component/test_helpers.rb +67 -50
  38. data/lib/view_component/translatable.rb +33 -32
  39. data/lib/view_component/version.rb +3 -3
  40. data/lib/view_component.rb +9 -6
  41. metadata +36 -556
  42. data/app/assets/vendor/prism.css +0 -4
  43. data/app/assets/vendor/prism.min.js +0 -12
  44. data/app/helpers/preview_helper.rb +0 -85
  45. data/app/views/view_components/_preview_source.html.erb +0 -17
  46. data/lib/rails/generators/tailwindcss/component_generator.rb +0 -11
  47. data/lib/view_component/capture_compatibility.rb +0 -44
  48. data/lib/view_component/component_error.rb +0 -6
  49. data/lib/view_component/rails/tasks/view_component.rake +0 -20
  50. data/lib/view_component/render_component_helper.rb +0 -10
  51. data/lib/view_component/render_component_to_string_helper.rb +0 -9
  52. data/lib/view_component/render_monkey_patch.rb +0 -13
  53. data/lib/view_component/render_to_string_monkey_patch.rb +0 -13
  54. data/lib/view_component/rendering_component_helper.rb +0 -9
  55. data/lib/view_component/rendering_monkey_patch.rb +0 -13
  56. data/lib/view_component/slotable_default.rb +0 -20
  57. data/lib/view_component/use_helpers.rb +0 -42
  58. /data/lib/{rails/generators → generators/view_component}/erb/templates/component.html.erb.tt +0 -0
  59. /data/lib/{rails/generators → generators/view_component}/haml/templates/component.html.haml.tt +0 -0
  60. /data/lib/{rails/generators → generators/view_component}/preview/templates/component_preview.rb.tt +0 -0
  61. /data/lib/{rails/generators → generators/view_component}/rspec/templates/component_spec.rb.tt +0 -0
  62. /data/lib/{rails/generators → generators/view_component}/slim/templates/component.html.slim.tt +0 -0
  63. /data/lib/{rails/generators → generators/view_component}/stimulus/templates/component_controller.js.tt +0 -0
  64. /data/lib/{rails/generators → generators/view_component}/stimulus/templates/component_controller.ts.tt +0 -0
  65. /data/lib/{rails/generators → generators/view_component}/tailwindcss/templates/component.html.erb.tt +0 -0
  66. /data/lib/{rails/generators → generators/view_component}/test_unit/templates/component_test.rb.tt +0 -0
@@ -1,53 +1,52 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "action_view/renderer/collection_renderer" if Rails.version.to_f >= 6.1
3
+ require "action_view/renderer/collection_renderer"
4
+ require "action_view/helpers/output_safety_helper"
4
5
 
5
6
  module ViewComponent
6
7
  class Collection
7
8
  include Enumerable
9
+ include ActionView::Helpers::OutputSafetyHelper
10
+
8
11
  attr_reader :component
9
12
 
10
13
  delegate :size, to: :@collection
11
14
 
12
- attr_accessor :__vc_original_view_context
15
+ def render_in(view_context, **_, &block)
16
+ rendered = components.map do |component|
17
+ component.render_in(view_context, &block)
18
+ end
19
+ safe_join(rendered, rendered_spacer(view_context))
20
+ end
13
21
 
14
- def set_original_view_context(view_context)
15
- self.__vc_original_view_context = view_context
22
+ def each(&block)
23
+ components.each(&block)
16
24
  end
17
25
 
18
- def render_in(view_context, &block)
19
- components.map do |component|
20
- component.set_original_view_context(__vc_original_view_context)
21
- component.render_in(view_context, &block)
22
- end.join(rendered_spacer(view_context)).html_safe
26
+ if defined?(Rails::VERSION) && Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR == 1
27
+ # Rails expects us to define `format` on all renderables,
28
+ # but we do not know the `format` of a ViewComponent until runtime.
29
+ def format
30
+ nil
31
+ end
23
32
  end
24
33
 
25
- def components
26
- return @components if defined? @components
34
+ private
27
35
 
36
+ # Always rebuild child component instances per render to avoid leaking
37
+ # request-scoped state from a previous render into a later one (GHSA).
38
+ def components
28
39
  iterator = ActionView::PartialIteration.new(@collection.size)
29
40
 
30
- component.validate_collection_parameter!(validate_default: true)
41
+ component.__vc_validate_collection_parameter!(validate_default: true)
31
42
 
32
- @components = @collection.map do |item|
33
- component.new(**component_options(item, iterator)).tap do |component|
43
+ @collection.map do |item|
44
+ component.new(**component_options(item, iterator)).tap do |_|
34
45
  iterator.iterate!
35
46
  end
36
47
  end
37
48
  end
38
49
 
39
- def each(&block)
40
- components.each(&block)
41
- end
42
-
43
- # Rails expects us to define `format` on all renderables,
44
- # but we do not know the `format` of a ViewComponent until runtime.
45
- def format
46
- nil
47
- end
48
-
49
- private
50
-
51
50
  def initialize(component, object, spacer_component, **options)
52
51
  @component = component
53
52
  @collection = collection_variable(object || [])
@@ -64,20 +63,19 @@ module ViewComponent
64
63
  end
65
64
 
66
65
  def component_options(item, iterator)
67
- item_options = {component.collection_parameter => item}
68
- item_options[component.collection_counter_parameter] = iterator.index if component.counter_argument_present?
69
- item_options[component.collection_iteration_parameter] = iterator.dup if component.iteration_argument_present?
66
+ item_options = {component.__vc_collection_parameter => item}
67
+ item_options[component.__vc_collection_counter_parameter] = iterator.index if component.__vc_counter_argument_present?
68
+ item_options[component.__vc_collection_iteration_parameter] = iterator.dup if component.__vc_iteration_argument_present?
70
69
 
71
70
  @options.merge(item_options)
72
71
  end
73
72
 
73
+ # Render the spacer through a fresh `dup` so a collection rendered multiple
74
+ # times always gets a clean spacer instance.
74
75
  def rendered_spacer(view_context)
75
- if @spacer_component
76
- @spacer_component.set_original_view_context(__vc_original_view_context)
77
- @spacer_component.render_in(view_context)
78
- else
79
- ""
80
- end
76
+ return "" unless @spacer_component
77
+
78
+ @spacer_component.dup.render_in(view_context)
81
79
  end
82
80
  end
83
81
  end
@@ -8,7 +8,7 @@ module ViewComponent
8
8
  # * true (a blocking mode which ensures thread safety when redefining the `call` method for components,
9
9
  # default in Rails development and test mode)
10
10
  # * false(a non-blocking mode, default in Rails production mode)
11
- class_attribute :development_mode, default: false
11
+ class_attribute :__vc_development_mode, default: false
12
12
 
13
13
  def initialize(component)
14
14
  @component = component
@@ -30,8 +30,8 @@ module ViewComponent
30
30
 
31
31
  gather_templates
32
32
 
33
- if self.class.development_mode && @templates.any?(&:requires_compiled_superclass?)
34
- @component.superclass.compile(raise_errors: raise_errors)
33
+ if self.class.__vc_development_mode && @templates.any?(&:requires_compiled_superclass?)
34
+ @component.superclass.__vc_compile(raise_errors: raise_errors)
35
35
  end
36
36
 
37
37
  if template_errors.present?
@@ -42,17 +42,36 @@ module ViewComponent
42
42
  end
43
43
 
44
44
  if raise_errors
45
- @component.validate_initialization_parameters!
46
- @component.validate_collection_parameter!
45
+ @component.__vc_validate_initialization_parameters!
46
+ @component.__vc_validate_collection_parameter!
47
47
  end
48
48
 
49
49
  define_render_template_for
50
50
 
51
- @component.register_default_slots
52
- @component.build_i18n_backend
51
+ @component.__vc_register_default_slots
52
+ @component.__vc_build_i18n_backend
53
53
 
54
54
  CompileCache.register(@component)
55
+
56
+ @component.after_compile
57
+ end
58
+ end
59
+
60
+ # @param requested_details [ActionView::TemplateDetails::Requested] i.e. locales, formats, variants
61
+ #
62
+ # @return all matching compiled templates, in priority order based on the requested details from LookupContext
63
+ def find_templates_for(requested_details)
64
+ filtered_templates = @templates.select do |template|
65
+ template.details.matches?(requested_details)
55
66
  end
67
+
68
+ if filtered_templates.count > 1
69
+ filtered_templates.sort_by! do |template|
70
+ template.details.sort_key_for(requested_details)
71
+ end
72
+ end
73
+
74
+ filtered_templates
56
75
  end
57
76
 
58
77
  private
@@ -64,40 +83,25 @@ module ViewComponent
64
83
  template.compile_to_component
65
84
  end
66
85
 
67
- method_body =
68
- if @templates.one?
69
- @templates.first.safe_method_name_call
70
- elsif (template = @templates.find(&:inline?))
71
- template.safe_method_name_call
72
- else
73
- branches = []
74
-
75
- @templates.each do |template|
76
- conditional =
77
- if template.inline_call?
78
- "variant&.to_sym == #{template.variant.inspect}"
79
- else
80
- [
81
- template.default_format? ? "(format == #{ViewComponent::Base::VC_INTERNAL_DEFAULT_FORMAT.inspect} || format.nil?)" : "format == #{template.format.inspect}",
82
- template.variant.nil? ? "variant.nil?" : "variant&.to_sym == #{template.variant.inspect}"
83
- ].join(" && ")
84
- end
85
-
86
- branches << [conditional, template.safe_method_name_call]
87
- end
86
+ @component.silence_redefinition_of_method(:render_template_for)
88
87
 
89
- out = branches.each_with_object(+"") do |(conditional, branch_body), memo|
90
- memo << "#{(!memo.present?) ? "if" : "elsif"} #{conditional}\n #{branch_body}\n"
88
+ if @templates.one?
89
+ template = @templates.first
90
+ safe_call = template.safe_method_name_call
91
+ @component.define_method(:render_template_for) do |_|
92
+ @current_template = template
93
+ instance_exec(&safe_call)
94
+ end
95
+ else
96
+ compiler = self
97
+ @component.define_method(:render_template_for) do |details|
98
+ if (@current_template = compiler.find_templates_for(details).first)
99
+ instance_exec(&@current_template.safe_method_name_call)
100
+ else
101
+ raise MissingTemplateError.new(self.class.name, details)
91
102
  end
92
- out << "else\n #{templates.find { _1.variant.nil? && _1.default_format? }.safe_method_name_call}\nend"
93
103
  end
94
-
95
- @component.silence_redefinition_of_method(:render_template_for)
96
- @component.class_eval <<-RUBY, __FILE__, __LINE__ + 1
97
- def render_template_for(variant = nil, format = nil)
98
- #{method_body}
99
104
  end
100
- RUBY
101
105
  end
102
106
 
103
107
  def template_errors
@@ -106,19 +110,16 @@ module ViewComponent
106
110
 
107
111
  errors << "Couldn't find a template file or inline render method for #{@component}." if @templates.empty?
108
112
 
109
- # We currently allow components to have both an inline call method and a template for a variant, with the
110
- # inline call method overriding the template. We should aim to change this in v4 to instead
111
- # raise an error.
112
- @templates.reject(&:inline_call?)
113
+ @templates
113
114
  .map { |template| [template.variant, template.format] }
114
115
  .tally
115
116
  .select { |_, count| count > 1 }
116
117
  .each do |tally|
117
- variant, this_format = tally.first
118
+ variant, this_format = tally.first
118
119
 
119
- variant_string = " for variant `#{variant}`" if variant.present?
120
+ variant_string = " for variant `#{variant}`" if variant.present?
120
121
 
121
- errors << "More than one #{this_format.upcase} template found#{variant_string} for #{@component}. "
122
+ errors << "More than one #{this_format.upcase} template found#{variant_string} for #{@component}. "
122
123
  end
123
124
 
124
125
  default_template_types = @templates.each_with_object(Set.new) do |template, memo|
@@ -168,30 +169,18 @@ module ViewComponent
168
169
 
169
170
  def gather_templates
170
171
  @templates ||=
171
- begin
172
+ if @component.__vc_inline_template.present?
173
+ [Template::Inline.new(
174
+ component: @component,
175
+ inline_template: @component.__vc_inline_template
176
+ )]
177
+ else
178
+ path_parser = ActionView::Resolver::PathParser.new
172
179
  templates = @component.sidecar_files(
173
180
  ActionView::Template.template_handler_extensions
174
181
  ).map do |path|
175
- # Extract format and variant from template filename
176
- this_format, variant =
177
- File
178
- .basename(path) # "variants_component.html+mini.watch.erb"
179
- .split(".")[1..-2] # ["html+mini", "watch"]
180
- .join(".") # "html+mini.watch"
181
- .split("+") # ["html", "mini.watch"]
182
- .map(&:to_sym) # [:html, :"mini.watch"]
183
-
184
- out = Template.new(
185
- component: @component,
186
- type: :file,
187
- path: path,
188
- lineno: 0,
189
- extension: path.split(".").last,
190
- this_format: this_format.to_s.split(".").last&.to_sym, # strip locale from this_format, see #2113
191
- variant: variant
192
- )
193
-
194
- out
182
+ details = path_parser.parse(path).details
183
+ Template::File.new(component: @component, path: path, details: details)
195
184
  end
196
185
 
197
186
  component_instance_methods_on_self = @component.instance_methods(false)
@@ -201,25 +190,11 @@ module ViewComponent
201
190
  ).flat_map { |ancestor| ancestor.instance_methods(false).grep(/^call(_|$)/) }
202
191
  .uniq
203
192
  .each do |method_name|
204
- templates << Template.new(
193
+ templates << Template::InlineCall.new(
205
194
  component: @component,
206
- type: :inline_call,
207
- this_format: ViewComponent::Base::VC_INTERNAL_DEFAULT_FORMAT,
208
- variant: method_name.to_s.include?("call_") ? method_name.to_s.sub("call_", "").to_sym : nil,
209
195
  method_name: method_name,
210
196
  defined_on_self: component_instance_methods_on_self.include?(method_name)
211
197
  )
212
- end
213
-
214
- if @component.inline_template.present?
215
- templates << Template.new(
216
- component: @component,
217
- type: :inline,
218
- path: @component.inline_template.path,
219
- lineno: @component.inline_template.lineno,
220
- source: @component.inline_template.source.dup,
221
- extension: @component.inline_template.language
222
- )
223
198
  end
224
199
 
225
200
  templates
@@ -13,29 +13,25 @@ module ViewComponent
13
13
  def defaults
14
14
  ActiveSupport::OrderedOptions.new.merge!({
15
15
  generate: default_generate_options,
16
- preview_controller: "ViewComponentsController",
17
- preview_route: "/rails/view_components",
18
- show_previews_source: false,
19
- instrumentation_enabled: false,
20
- use_deprecated_instrumentation_name: true,
21
- render_monkey_patch_enabled: true,
22
- view_component_path: "app/components",
23
- component_parent_class: nil,
24
- show_previews: Rails.env.development? || Rails.env.test?,
25
- preview_paths: default_preview_paths,
26
- test_controller: "ApplicationController",
27
- default_preview_layout: nil,
28
- capture_compatibility_patch_enabled: false
16
+ previews: default_previews_options,
17
+ instrumentation_enabled: false
29
18
  })
30
19
  end
31
20
 
32
21
  # @!attribute generate
22
+ #
33
23
  # @return [ActiveSupport::OrderedOptions]
34
24
  # The subset of configuration options relating to generators.
35
25
  #
36
26
  # All options under this namespace default to `false` unless otherwise
37
27
  # stated.
38
28
  #
29
+ # #### `#path`
30
+ #
31
+ # Where to put generated components. Defaults to `app/components`:
32
+ #
33
+ # config.view_component.generate.path = "lib/components"
34
+ #
39
35
  # #### `#sidecar`
40
36
  #
41
37
  # Always generate a component with a sidecar directory:
@@ -83,96 +79,58 @@ module ViewComponent
83
79
  #
84
80
  # Required when there is more than one path defined in preview_paths.
85
81
  # Defaults to `""`. If this is blank, the generator will use
86
- # `ViewComponent.config.preview_paths` if defined,
82
+ # `ViewComponent.config.previews.paths` if defined,
87
83
  # `"test/components/previews"` otherwise
88
84
  #
89
85
  # #### `#use_component_path_for_rspec_tests`
90
86
  #
91
- # Whether to use the `config.view_component_path` when generating new
87
+ # Whether to use `config.generate.path` when generating new
92
88
  # RSpec component tests:
93
89
  #
94
90
  # config.view_component.generate.use_component_path_for_rspec_tests = true
95
91
  #
96
- # When set to `true`, the generator will use the `view_component_path` to
92
+ # When set to `true`, the generator will use the `path` to
97
93
  # decide where to generate the new RSpec component test.
98
- # For example, if the `view_component_path` is
94
+ # For example, if the `path` is
99
95
  # `app/views/components`, then the generator will create a new spec file
100
96
  # in `spec/views/components/` rather than the default `spec/components/`.
101
97
 
102
- # @!attribute preview_controller
103
- # @return [String]
104
- # The controller used for previewing components.
105
- # Defaults to `ViewComponentsController`.
106
-
107
- # @!attribute preview_route
108
- # @return [String]
109
- # The entry route for component previews.
110
- # Defaults to `"/rails/view_components"`.
111
-
112
- # @!attribute show_previews_source
113
- # @return [Boolean]
114
- # Whether to display source code previews in component previews.
115
- # Defaults to `false`.
98
+ # @!attribute previews
99
+ #
100
+ # @return [ActiveSupport::OrderedOptions]
101
+ # The subset of configuration options relating to previews.
102
+ #
103
+ # #### `#controller`
104
+ #
105
+ # The controller used for previewing components. Defaults to `ViewComponentsController`:
106
+ #
107
+ # config.view_component.previews.controller = "MyPreviewController"
108
+ #
109
+ # #### `#route`
110
+ #
111
+ # The entry route for component previews. Defaults to `/rails/view_components`:
112
+ #
113
+ # config.view_component.previews.route = "/my_previews"
114
+ #
115
+ # #### `#enabled`
116
+ #
117
+ # Whether component previews are enabled. Defaults to `true` in development and test environments:
118
+ #
119
+ # config.view_component.previews.enabled = false
120
+ #
121
+ # #### `#default_layout`
122
+ #
123
+ # A custom default layout used for the previews index page and individual previews. Defaults to `nil`:
124
+ #
125
+ # config.view_component.previews.default_layout = "preview_layout"
126
+ #
116
127
 
117
128
  # @!attribute instrumentation_enabled
129
+ #
118
130
  # @return [Boolean]
119
131
  # Whether ActiveSupport notifications are enabled.
120
132
  # Defaults to `false`.
121
133
 
122
- # @!attribute use_deprecated_instrumentation_name
123
- # @return [Boolean]
124
- # Whether ActiveSupport Notifications use the private name `"!render.view_component"`
125
- # or are made more publicly available via `"render.view_component"`.
126
- # Will be removed in next major version.
127
- # Defaults to `true`.
128
-
129
- # @!attribute render_monkey_patch_enabled
130
- # @return [Boolean] Whether the #render method should be monkey patched.
131
- # If this is disabled, use `#render_component` or
132
- # `#render_component_to_string` instead.
133
- # Defaults to `true`.
134
-
135
- # @!attribute view_component_path
136
- # @return [String]
137
- # The path in which components, their templates, and their sidecars should
138
- # be stored.
139
- # Defaults to `"app/components"`.
140
-
141
- # @!attribute component_parent_class
142
- # @return [String]
143
- # The parent class from which generated components will inherit.
144
- # Defaults to `nil`. If this is falsy, generators will use
145
- # `"ApplicationComponent"` if defined, `"ViewComponent::Base"` otherwise.
146
-
147
- # @!attribute show_previews
148
- # @return [Boolean]
149
- # Whether component previews are enabled.
150
- # Defaults to `true` in development and test environments.
151
-
152
- # @!attribute preview_paths
153
- # @return [Array<String>]
154
- # The locations in which component previews will be looked up.
155
- # Defaults to `['test/components/previews']` relative to your Rails root.
156
-
157
- # @!attribute test_controller
158
- # @return [String]
159
- # The controller used for testing components.
160
- # Can also be configured on a per-test basis using `#with_controller_class`.
161
- # Defaults to `ApplicationController`.
162
-
163
- # @!attribute default_preview_layout
164
- # @return [String]
165
- # A custom default layout used for the previews index page and individual
166
- # previews.
167
- # Defaults to `nil`. If this is falsy, `"component_preview"` is used.
168
-
169
- # @!attribute capture_compatibility_patch_enabled
170
- # @return [Boolean]
171
- # Enables the experimental capture compatibility patch that makes ViewComponent
172
- # compatible with forms, capture, and other built-ins.
173
- # previews.
174
- # Defaults to `false`.
175
-
176
134
  def default_preview_paths
177
135
  (default_rails_preview_paths + default_rails_engines_preview_paths).uniq
178
136
  end
@@ -200,11 +158,23 @@ module ViewComponent
200
158
  def default_generate_options
201
159
  options = ActiveSupport::OrderedOptions.new(false)
202
160
  options.preview_path = ""
161
+ options.path = "app/components"
162
+ options
163
+ end
164
+
165
+ def default_previews_options
166
+ options = ActiveSupport::OrderedOptions.new
167
+ options.controller = "ViewComponentsController"
168
+ options.route = "/rails/view_components"
169
+ options.enabled = defined?(Rails.env) && (Rails.env.development? || Rails.env.test?)
170
+ options.default_layout = nil
171
+ options.paths = default_preview_paths
203
172
  options
204
173
  end
205
174
  end
206
175
 
207
176
  # @!attribute current
177
+ #
208
178
  # @return [ViewComponent::Config]
209
179
  # Returns the current ViewComponent::Config. This is persisted against this
210
180
  # class so that config options remain accessible before the rest of
@@ -4,14 +4,30 @@ module ViewComponent
4
4
  module Configurable
5
5
  extend ActiveSupport::Concern
6
6
 
7
- included do
8
- next if respond_to?(:config) && config.respond_to?(:view_component) && config.respond_to_missing?(:test_controller)
7
+ class_methods do
8
+ def config
9
+ @_config ||= if respond_to?(:superclass) && superclass.respond_to?(:config)
10
+ superclass.config.inheritable_copy
11
+ else
12
+ ActiveSupport::OrderedOptions.new
13
+ end
14
+ end
15
+
16
+ def configure
17
+ yield config
18
+ end
19
+ end
9
20
 
10
- include ActiveSupport::Configurable
21
+ included do
22
+ next if respond_to?(:config) && config.respond_to?(:view_component) && config.respond_to_missing?(:instrumentation_enabled)
11
23
 
12
24
  configure do |config|
13
25
  config.view_component ||= ActiveSupport::InheritableOptions.new
14
26
  end
27
+
28
+ def config
29
+ self.class.config
30
+ end
15
31
  end
16
32
  end
17
33
  end
@@ -3,6 +3,6 @@
3
3
  require "active_support/deprecation"
4
4
 
5
5
  module ViewComponent
6
- DEPRECATION_HORIZON = "4.0.0"
6
+ DEPRECATION_HORIZON = "5.0.0"
7
7
  Deprecation = ActiveSupport::Deprecation.new(DEPRECATION_HORIZON, "ViewComponent")
8
8
  end