view_component 2.51.0 → 2.54.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: 163414625d8e2df6f2afddc821a6c849aef71cc8cd1ba458a533d90840e545cc
4
- data.tar.gz: 3518269c96d4e62d3bcedae22871c7b260d2abf42e0c1e32703115185e69eeea
3
+ metadata.gz: b4b1f5b50f3ff33ba025588ac11f83362684484dae4aafea6b3be2a866944b3c
4
+ data.tar.gz: bdc4bf615413d7b3ab8e936c6df0ffd12b8915e03f44c5a8f6ad6989eb5fb5b5
5
5
  SHA512:
6
- metadata.gz: 531785b833a4d1a83b6929095b6a6a34076cb6576820884487a247d70c37bde2fedc47914633674c59a888dc7270526660e121f4980f2bcb85625ed6009607c4
7
- data.tar.gz: e4c8b2694c7f70c529d56dcced18f66d511e909e1f36abc604a58402d1332caecde59d40c065668454af8abf6d53578469539ac1f4366650ad286856b50776fa
6
+ metadata.gz: 98ce9d4ac6888dd6b6fed5987f2a6a95b6589c12930886bb35ec1009cdf385f507d3b199e63d27bd93f2125294cc5b8f7e085de27fd67ed6e45c613534bc41cb
7
+ data.tar.gz: '058bdd0c93a6af548eb77feaafb432fe5a6fa9ded2730b4576b7e87c713a403c81e6db5340aa70a78444b4ce8b9b95f729294391e98ff946ecc50f4c86837d48'
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module PreviewActions
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ prepend_view_path File.expand_path("../../../views", __dir__)
9
+
10
+ around_action :set_locale, only: :previews
11
+ before_action :find_preview, only: :previews
12
+ before_action :require_local!, unless: :show_previews?
13
+
14
+ if respond_to?(:content_security_policy)
15
+ content_security_policy(false)
16
+ end
17
+ end
18
+
19
+ def index
20
+ @previews = ViewComponent::Preview.all
21
+ @page_title = "Component Previews"
22
+ render "view_components/index", **determine_layout
23
+ end
24
+
25
+ def previews
26
+ if params[:path] == @preview.preview_name
27
+ @page_title = "Component Previews for #{@preview.preview_name}"
28
+ render "view_components/previews", **determine_layout
29
+ else
30
+ prepend_application_view_paths
31
+ prepend_preview_examples_view_path
32
+ @example_name = File.basename(params[:path])
33
+ @render_args = @preview.render_args(@example_name, params: params.permit!)
34
+ layout = determine_layout(@render_args[:layout], prepend_views: false)[:layout]
35
+ locals = @render_args[:locals]
36
+ opts = {}
37
+ opts[:layout] = layout if layout.present? || layout == false
38
+ opts[:locals] = locals if locals.present?
39
+ render "view_components/preview", opts # rubocop:disable GitHub/RailsControllerRenderLiteral
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def default_preview_layout # :doc:
46
+ ViewComponent::Base.default_preview_layout
47
+ end
48
+
49
+ def show_previews? # :doc:
50
+ ViewComponent::Base.show_previews
51
+ end
52
+
53
+ def find_preview # :doc:
54
+ candidates = []
55
+ params[:path].to_s.scan(%r{/|$}) { candidates << $` }
56
+ preview = candidates.detect { |candidate| ViewComponent::Preview.exists?(candidate) }
57
+
58
+ if preview
59
+ @preview = ViewComponent::Preview.find(preview)
60
+ else
61
+ raise AbstractController::ActionNotFound, "Component preview '#{params[:path]}' not found."
62
+ end
63
+ end
64
+
65
+ def set_locale
66
+ I18n.with_locale(params[:locale] || I18n.default_locale) do
67
+ yield
68
+ end
69
+ end
70
+
71
+ # Returns either {} or {layout: value} depending on configuration
72
+ def determine_layout(layout_override = nil, prepend_views: true)
73
+ return {} unless defined?(Rails.root)
74
+
75
+ layout_declaration = {}
76
+
77
+ if !layout_override.nil?
78
+ # Allow component-level override, even if false (thus no layout rendered)
79
+ layout_declaration[:layout] = layout_override
80
+ elsif default_preview_layout.present?
81
+ layout_declaration[:layout] = default_preview_layout
82
+ end
83
+
84
+ prepend_application_view_paths if layout_declaration[:layout].present? && prepend_views
85
+
86
+ layout_declaration
87
+ end
88
+
89
+ def prepend_application_view_paths
90
+ prepend_view_path Rails.root.join("app/views") if defined?(Rails.root)
91
+ end
92
+
93
+ def prepend_preview_examples_view_path
94
+ prepend_view_path(ViewComponent::Base.preview_paths)
95
+ end
96
+ end
97
+ end
@@ -3,91 +3,5 @@
3
3
  require "rails/application_controller"
4
4
 
5
5
  class ViewComponentsController < Rails::ApplicationController # :nodoc:
6
- prepend_view_path File.expand_path("../views", __dir__)
7
-
8
- around_action :set_locale, only: :previews
9
- before_action :find_preview, only: :previews
10
- before_action :require_local!, unless: :show_previews?
11
-
12
- if respond_to?(:content_security_policy)
13
- content_security_policy(false)
14
- end
15
-
16
- def index
17
- @previews = ViewComponent::Preview.all
18
- @page_title = "Component Previews"
19
- render "view_components/index", **determine_layout
20
- end
21
-
22
- def previews
23
- if params[:path] == @preview.preview_name
24
- @page_title = "Component Previews for #{@preview.preview_name}"
25
- render "view_components/previews", **determine_layout
26
- else
27
- prepend_application_view_paths
28
- prepend_preview_examples_view_path
29
- @example_name = File.basename(params[:path])
30
- @render_args = @preview.render_args(@example_name, params: params.permit!)
31
- layout = determine_layout(@render_args[:layout], prepend_views: false)[:layout]
32
- locals = @render_args[:locals]
33
- opts = {}
34
- opts[:layout] = layout if layout.present? || layout == false
35
- opts[:locals] = locals if locals.present?
36
- render "view_components/preview", opts # rubocop:disable GitHub/RailsControllerRenderLiteral
37
- end
38
- end
39
-
40
- private
41
-
42
- def default_preview_layout # :doc:
43
- ViewComponent::Base.default_preview_layout
44
- end
45
-
46
- def show_previews? # :doc:
47
- ViewComponent::Base.show_previews
48
- end
49
-
50
- def find_preview # :doc:
51
- candidates = []
52
- params[:path].to_s.scan(%r{/|$}) { candidates << $` }
53
- preview = candidates.detect { |candidate| ViewComponent::Preview.exists?(candidate) }
54
-
55
- if preview
56
- @preview = ViewComponent::Preview.find(preview)
57
- else
58
- raise AbstractController::ActionNotFound, "Component preview '#{params[:path]}' not found."
59
- end
60
- end
61
-
62
- def set_locale
63
- I18n.with_locale(params[:locale] || I18n.default_locale) do
64
- yield
65
- end
66
- end
67
-
68
- # Returns either {} or {layout: value} depending on configuration
69
- def determine_layout(layout_override = nil, prepend_views: true)
70
- return {} unless defined?(Rails.root)
71
-
72
- layout_declaration = {}
73
-
74
- if !layout_override.nil?
75
- # Allow component-level override, even if false (thus no layout rendered)
76
- layout_declaration[:layout] = layout_override
77
- elsif default_preview_layout.present?
78
- layout_declaration[:layout] = default_preview_layout
79
- end
80
-
81
- prepend_application_view_paths if layout_declaration[:layout].present? && prepend_views
82
-
83
- layout_declaration
84
- end
85
-
86
- def prepend_application_view_paths
87
- prepend_view_path Rails.root.join("app/views") if defined?(Rails.root)
88
- end
89
-
90
- def prepend_preview_examples_view_path
91
- prepend_view_path(ViewComponent::Base.preview_paths)
92
- end
6
+ include ViewComponent::PreviewActions
93
7
  end
data/docs/CHANGELOG.md CHANGED
@@ -3,10 +3,75 @@ layout: default
3
3
  title: Changelog
4
4
  ---
5
5
 
6
+ <!-- Add unreleased changes under the "main" heading. -->
7
+
6
8
  # Changelog
7
9
 
8
10
  ## main
9
11
 
12
+ ## 2.54.0
13
+
14
+ * Add `with_*` slot API for defining slots. Note: we plan to deprecate the non `with_*` API for slots in an upcoming release.
15
+
16
+ *Blake Williams*
17
+
18
+ * Add QuickNode to list of companies that heavily rely on ViewComponent.
19
+
20
+ *Luc Castera*
21
+
22
+ * Include the `Translatable` module by default.
23
+
24
+ *Elia Schito*
25
+
26
+ * Update docs dependencies.
27
+
28
+ *Joel Hawksley*
29
+
30
+ ## 2.53.0
31
+
32
+ * Add support for relative I18n scopes to translations.
33
+
34
+ *Elia Schito*
35
+
36
+ * Update CI configuration to use latest Rails 7.0.
37
+
38
+ *Hans Lemuet*
39
+
40
+ * Document how to use blocks with lambda slots.
41
+
42
+ *Sam Partington*
43
+
44
+ * Skip Rails 5.2 in local test environment if using incompatible Ruby version.
45
+
46
+ *Cameron Dutro*, *Blake Williams*, *Joel Hawksley*
47
+
48
+ * Improve landing page documentation.
49
+
50
+ *Jason Swett*
51
+
52
+ * Add Bearer to list of companies that heavily rely on ViewComponent.
53
+
54
+ *Yaroslav Shmarov*
55
+
56
+ * Add articles to resources page.
57
+
58
+ *Joel Hawksley*
59
+
60
+ ## 2.52.0
61
+
62
+ * Add ADR for separate slot getter/setter API.
63
+
64
+ *Blake Williams*
65
+
66
+ * Add the option to use a "global" output buffer so `form_for` and friends can be used with view components.
67
+
68
+ *Cameron Dutro*, *Blake Williams*
69
+
70
+ * Fix fragment caching in partials when global output buffer is enabled.
71
+ * Fix template inheritance when eager loading is disabled.
72
+
73
+ *Cameron Dutro*
74
+
10
75
  ## 2.51.0
11
76
 
12
77
  * Update the docs only when releasing a new version.
@@ -109,10 +174,14 @@ title: Changelog
109
174
 
110
175
  *Joel Hawksley*
111
176
 
112
- * Add Ruby 3.1 and Rails 7.0 to CI
177
+ * Add Ruby 3.1 and Rails 7.0 to CI.
113
178
 
114
179
  *Peter Goldstein*
115
180
 
181
+ * Move preview logic to module for easier app integration.
182
+
183
+ *Sammy Henningsson*
184
+
116
185
  ## 2.48.0
117
186
 
118
187
  * Correct path in example test command in Contributing docs.
@@ -17,6 +17,7 @@ module ViewComponent
17
17
  include ViewComponent::ContentAreas
18
18
  include ViewComponent::Previewable
19
19
  include ViewComponent::SlotableV2
20
+ include ViewComponent::Translatable
20
21
  include ViewComponent::WithContentHelper
21
22
 
22
23
  ViewContextCalledBeforeRenderError = Class.new(StandardError)
@@ -69,6 +70,8 @@ module ViewComponent
69
70
  @view_context = view_context
70
71
  self.__vc_original_view_context ||= view_context
71
72
 
73
+ @output_buffer = ActionView::OutputBuffer.new unless @global_buffer_in_use
74
+
72
75
  @lookup_context ||= view_context.lookup_context
73
76
 
74
77
  # required for path helpers in older Rails versions
@@ -102,7 +105,7 @@ module ViewComponent
102
105
  before_render
103
106
 
104
107
  if render?
105
- render_template_for(@__vc_variant).to_s + _output_postamble
108
+ perform_render
106
109
  else
107
110
  ""
108
111
  end
@@ -110,6 +113,10 @@ module ViewComponent
110
113
  @current_template = old_current_template
111
114
  end
112
115
 
116
+ def perform_render
117
+ render_template_for(@__vc_variant).to_s + _output_postamble
118
+ end
119
+
113
120
  # :nocov:
114
121
  def render_template_for(variant = nil)
115
122
  # Force compilation here so the compiler always redefines render_template_for.
@@ -421,6 +428,22 @@ module ViewComponent
421
428
  # `compile` defines
422
429
  compile
423
430
 
431
+ # Give the child its own personal #render_template_for to protect against the case when
432
+ # eager loading is disabled and the parent component is rendered before the child. In
433
+ # such a scenario, the parent will override ViewComponent::Base#render_template_for,
434
+ # meaning it will not be called for any children and thus not compile their templates.
435
+ if !child.instance_methods(false).include?(:render_template_for) && !child.compiled?
436
+ child.class_eval <<~RUBY, __FILE__, __LINE__ + 1
437
+ def render_template_for(variant = nil)
438
+ # Force compilation here so the compiler always redefines render_template_for.
439
+ # This is mostly a safeguard to prevent infinite recursion.
440
+ self.class.compile(raise_errors: true, force: true)
441
+ # .compile replaces this method; call the new one
442
+ render_template_for(variant)
443
+ end
444
+ RUBY
445
+ end
446
+
424
447
  # If Rails application is loaded, add application url_helpers to the component context
425
448
  # we need to check this to use this gem as a dependency
426
449
  if defined?(Rails) && Rails.application
@@ -68,9 +68,8 @@ module ViewComponent
68
68
  component_class.send(:remove_method, method_name.to_sym)
69
69
  end
70
70
 
71
- component_class.class_eval <<-RUBY, template[:path], -1
71
+ component_class.class_eval <<-RUBY, template[:path], 0
72
72
  def #{method_name}
73
- @output_buffer = ActionView::OutputBuffer.new
74
73
  #{compiled_template(template[:path])}
75
74
  end
76
75
  RUBY
@@ -78,6 +77,7 @@ module ViewComponent
78
77
 
79
78
  define_render_template_for
80
79
 
80
+ component_class.build_i18n_backend
81
81
  component_class._after_compile
82
82
 
83
83
  CompileCache.register(component_class)
@@ -20,6 +20,7 @@ module ViewComponent
20
20
  options.instrumentation_enabled = false if options.instrumentation_enabled.nil?
21
21
  options.preview_route ||= ViewComponent::Base.preview_route
22
22
  options.preview_controller ||= ViewComponent::Base.preview_controller
23
+ options.use_global_output_buffer = false if options.use_global_output_buffer.nil?
23
24
 
24
25
  if options.show_previews
25
26
  options.preview_paths << "#{Rails.root}/test/components/previews" if defined?(Rails.root) && Dir.exist?(
@@ -57,6 +58,21 @@ module ViewComponent
57
58
  end
58
59
  end
59
60
 
61
+ initializer "view_component.enable_global_output_buffer" do |app|
62
+ ActiveSupport.on_load(:view_component) do
63
+ env_use_gob = ENV.fetch("VIEW_COMPONENT_USE_GLOBAL_OUTPUT_BUFFER", "false") == "true"
64
+ config_use_gob = app.config.view_component.use_global_output_buffer
65
+
66
+ if config_use_gob || env_use_gob
67
+ # :nocov:
68
+ app.config.view_component.use_global_output_buffer = true
69
+ ViewComponent::Base.prepend(ViewComponent::GlobalOutputBuffer)
70
+ ActionView::Base.prepend(ViewComponent::GlobalOutputBuffer::ActionViewMods)
71
+ # :nocov:
72
+ end
73
+ end
74
+ end
75
+
60
76
  initializer "view_component.set_autoload_paths" do |app|
61
77
  options = app.config.view_component
62
78
 
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ module GlobalOutputBuffer
5
+ def render_in(view_context, &block)
6
+ unless view_context.output_buffer.is_a?(OutputBufferStack)
7
+ # use instance_variable_set here to avoid triggering the code in the #output_buffer= method below
8
+ view_context.instance_variable_set(:@output_buffer, OutputBufferStack.new(view_context.output_buffer))
9
+ end
10
+
11
+ @output_buffer = view_context.output_buffer
12
+ @global_buffer_in_use = true
13
+
14
+ super(view_context, &block)
15
+ end
16
+
17
+ def perform_render
18
+ # HAML unhelpfully assigns to @output_buffer directly, so we hold onto a reference to
19
+ # it and restore @output_buffer when the HAML engine is finished. In non-HAML cases,
20
+ # @output_buffer and orig_buf will point to the same object, making the reassignment
21
+ # statements no-ops.
22
+ orig_buf = @output_buffer
23
+ @output_buffer.push
24
+ result = render_template_for(@__vc_variant).to_s + _output_postamble
25
+ @output_buffer = orig_buf
26
+ @output_buffer.pop
27
+ result
28
+ end
29
+
30
+ def output_buffer=(other_buffer)
31
+ @output_buffer.replace(other_buffer)
32
+ end
33
+
34
+ def with_output_buffer(buf = nil)
35
+ unless buf
36
+ buf = ActionView::OutputBuffer.new
37
+ if output_buffer && output_buffer.respond_to?(:encoding)
38
+ buf.force_encoding(output_buffer.encoding)
39
+ end
40
+ end
41
+
42
+ output_buffer.push(buf)
43
+ result = nil
44
+
45
+ begin
46
+ yield
47
+ ensure
48
+ # assign result here to avoid a return statement, which will
49
+ # immediately return to the caller and swallow any errors
50
+ result = output_buffer.pop
51
+ end
52
+
53
+ result
54
+ end
55
+
56
+ module ActionViewMods
57
+ def output_buffer=(other_buffer)
58
+ if @output_buffer.is_a?(OutputBufferStack)
59
+ @output_buffer.replace(other_buffer)
60
+ else
61
+ super
62
+ end
63
+ end
64
+
65
+ def with_output_buffer(buf = nil)
66
+ unless buf
67
+ buf = ActionView::OutputBuffer.new
68
+ if @output_buffer && @output_buffer.respond_to?(:encoding)
69
+ buf.force_encoding(@output_buffer.encoding)
70
+ end
71
+ end
72
+
73
+ result = nil
74
+
75
+ if @output_buffer.is_a?(OutputBufferStack)
76
+ @output_buffer.push(buf)
77
+
78
+ begin
79
+ yield
80
+ ensure
81
+ result = @output_buffer.pop
82
+ end
83
+
84
+ result
85
+ else
86
+ @output_buffer, old_buffer = buf, output_buffer
87
+
88
+ begin
89
+ yield
90
+ ensure
91
+ @output_buffer = old_buffer
92
+ end
93
+
94
+ buf
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponent
4
+ class OutputBufferStack
5
+ delegate_missing_to :@current_buffer
6
+ delegate :presence, :present?, :html_safe?, to: :@current_buffer
7
+
8
+ attr_reader :buffer_stack
9
+
10
+ def self.make_frame(*args)
11
+ ActionView::OutputBuffer.new(*args)
12
+ end
13
+
14
+ def initialize(initial_buffer = nil)
15
+ if initial_buffer.is_a?(self.class)
16
+ @current_buffer = self.class.make_frame(initial_buffer.current)
17
+ @buffer_stack = [*initial_buffer.buffer_stack[0..-2], @current_buffer]
18
+ else
19
+ @current_buffer = initial_buffer || self.class.make_frame
20
+ @buffer_stack = [@current_buffer]
21
+ end
22
+ end
23
+
24
+ def replace(buffer)
25
+ return if self == buffer
26
+
27
+ @current_buffer = buffer.current
28
+ @buffer_stack = buffer.buffer_stack
29
+ end
30
+
31
+ def append=(arg)
32
+ @current_buffer.append = arg
33
+ end
34
+
35
+ def safe_append=(arg)
36
+ @current_buffer.safe_append = arg
37
+ end
38
+
39
+ def safe_concat(arg)
40
+ # rubocop:disable Rails/OutputSafety
41
+ @current_buffer.safe_concat(arg)
42
+ # rubocop:enable Rails/OutputSafety
43
+ end
44
+
45
+ def length
46
+ @current_buffer.length
47
+ end
48
+
49
+ def push(buffer = nil)
50
+ buffer ||= self.class.make_frame
51
+ @buffer_stack.push(buffer)
52
+ @current_buffer = buffer
53
+ end
54
+
55
+ def pop
56
+ @buffer_stack.pop.tap do
57
+ @current_buffer = @buffer_stack.last
58
+ end
59
+ end
60
+
61
+ def to_s
62
+ @current_buffer
63
+ end
64
+
65
+ alias_method :current, :to_s
66
+ end
67
+ end
@@ -45,10 +45,16 @@ module ViewComponent
45
45
  "#{slot_name}_#{poly_type}"
46
46
  end
47
47
 
48
+ # Deprecated: Will be removed in 3.0
48
49
  define_method(setter_name) do |*args, &block|
49
50
  set_polymorphic_slot(slot_name, poly_type, *args, &block)
50
51
  end
51
52
  ruby2_keywords(setter_name.to_sym) if respond_to?(:ruby2_keywords, true)
53
+
54
+ define_method("with_#{setter_name}") do |*args, &block|
55
+ set_polymorphic_slot(slot_name, poly_type, *args, &block)
56
+ end
57
+ ruby2_keywords(:"with_#{setter_name}") if respond_to?(:ruby2_keywords, true)
52
58
  end
53
59
 
54
60
  self.registered_slots[slot_name] = {
@@ -45,18 +45,12 @@ module ViewComponent
45
45
  if defined?(@__vc_content_set_by_with_content)
46
46
  @__vc_component_instance.with_content(@__vc_content_set_by_with_content)
47
47
 
48
- view_context.capture do
49
- @__vc_component_instance.render_in(view_context)
50
- end
48
+ @__vc_component_instance.render_in(view_context)
51
49
  elsif defined?(@__vc_content_block)
52
- view_context.capture do
53
- # render_in is faster than `parent.render`
54
- @__vc_component_instance.render_in(view_context, &@__vc_content_block)
55
- end
50
+ # render_in is faster than `parent.render`
51
+ @__vc_component_instance.render_in(view_context, &@__vc_content_block)
56
52
  else
57
- view_context.capture do
58
- @__vc_component_instance.render_in(view_context)
59
- end
53
+ @__vc_component_instance.render_in(view_context)
60
54
  end
61
55
  elsif defined?(@__vc_content)
62
56
  @__vc_content
@@ -61,20 +61,26 @@ module ViewComponent
61
61
  # = Setting sub-component content
62
62
  #
63
63
  # Consumers of the component can render a sub-component by calling a
64
- # helper method with the same name as the slot.
64
+ # helper method with the same name as the slot prefixed with `with_`.
65
65
  #
66
66
  # <%= render_inline(MyComponent.new) do |component| %>
67
- # <% component.header(classes: "Foo") do %>
67
+ # <% component.with_header(classes: "Foo") do %>
68
68
  # <p>Bar</p>
69
69
  # <% end %>
70
70
  # <% end %>
71
71
  def renders_one(slot_name, callable = nil)
72
72
  validate_singular_slot_name(slot_name)
73
73
 
74
+ define_method :"with_#{slot_name}" do |*args, &block|
75
+ set_slot(slot_name, nil, *args, &block)
76
+ end
77
+ ruby2_keywords(:"with_#{slot_name}") if respond_to?(:ruby2_keywords, true)
78
+
74
79
  define_method slot_name do |*args, &block|
75
80
  if args.empty? && block.nil?
76
81
  get_slot(slot_name)
77
82
  else
83
+ # Deprecated: Will remove in 3.0
78
84
  set_slot(slot_name, nil, *args, &block)
79
85
  end
80
86
  end
@@ -112,15 +118,15 @@ module ViewComponent
112
118
  # = Setting sub-component content
113
119
  #
114
120
  # Consumers of the component can set the content of a slot by calling a
115
- # helper method with the same name as the slot. The method can be
116
- # called multiple times to append to the slot.
121
+ # helper method with the same name as the slot prefixed with `with_`. The
122
+ # method can be called multiple times to append to the slot.
117
123
  #
118
124
  # <%= render_inline(MyComponent.new) do |component| %>
119
- # <% component.item(name: "Foo") do %>
125
+ # <% component.with_item(name: "Foo") do %>
120
126
  # <p>One</p>
121
127
  # <% end %>
122
128
  #
123
- # <% component.item(name: "Bar") do %>
129
+ # <% component.with_item(name: "Bar") do %>
124
130
  # <p>two</p>
125
131
  # <% end %>
126
132
  # <% end %>
@@ -132,17 +138,32 @@ module ViewComponent
132
138
  # Define setter for singular names
133
139
  # for example `renders_many :items` allows fetching all tabs with
134
140
  # `component.tabs` and setting a tab with `component.tab`
141
+ #
142
+ # Deprecated: Will remove in 3.0
135
143
  define_method singular_name do |*args, &block|
136
144
  set_slot(slot_name, nil, *args, &block)
137
145
  end
138
146
  ruby2_keywords(singular_name.to_sym) if respond_to?(:ruby2_keywords, true)
139
147
 
148
+ define_method :"with_#{singular_name}" do |*args, &block|
149
+ set_slot(slot_name, nil, *args, &block)
150
+ end
151
+ ruby2_keywords(:"with_#{singular_name}") if respond_to?(:ruby2_keywords, true)
152
+
153
+ define_method :"with_#{slot_name}" do |collection_args = nil, &block|
154
+ collection_args.map do |args|
155
+ set_slot(slot_name, nil, **args, &block)
156
+ end
157
+ end
158
+ ruby2_keywords(:"with_#{slot_name}") if respond_to?(:ruby2_keywords, true)
159
+
140
160
  # Instantiates and and adds multiple slots forwarding the first
141
161
  # argument to each slot constructor
142
162
  define_method slot_name do |collection_args = nil, &block|
143
163
  if collection_args.nil? && block.nil?
144
164
  get_slot(slot_name)
145
165
  else
166
+ # Deprecated: Will remove in 3.0
146
167
  collection_args.map do |args|
147
168
  set_slot(slot_name, nil, **args, &block)
148
169
  end
@@ -3,7 +3,6 @@
3
3
  require "erb"
4
4
  require "set"
5
5
  require "i18n"
6
- require "action_view/helpers/translation_helper"
7
6
  require "active_support/concern"
8
7
 
9
8
  module ViewComponent
@@ -21,9 +20,7 @@ module ViewComponent
21
20
  @i18n_scope ||= virtual_path.sub(%r{^/}, "").gsub(%r{/_?}, ".")
22
21
  end
23
22
 
24
- def _after_compile
25
- super
26
-
23
+ def build_i18n_backend
27
24
  return if CompileCache.compiled? self
28
25
 
29
26
  if (translation_files = _sidecar_files(%w[yml yaml])).any?
@@ -68,7 +65,10 @@ module ViewComponent
68
65
  return key.map { |k| translate(k, **options) } if key.is_a?(Array)
69
66
 
70
67
  locale = options.delete(:locale) || ::I18n.locale
68
+ scope = options.delete(:scope)
69
+ scope = scope.join(".") if scope.is_a? Array
71
70
  key = key&.to_s unless key.is_a?(String)
71
+ key = "#{scope}.#{key}" if scope
72
72
  key = "#{i18n_scope}#{key}" if key.start_with?(".")
73
73
 
74
74
  if HTML_SAFE_TRANSLATION_KEY.match?(key)
@@ -3,7 +3,7 @@
3
3
  module ViewComponent
4
4
  module VERSION
5
5
  MAJOR = 2
6
- MINOR = 51
6
+ MINOR = 54
7
7
  PATCH = 0
8
8
 
9
9
  STRING = [MAJOR, MINOR, PATCH].join(".")
@@ -11,7 +11,9 @@ module ViewComponent
11
11
  autoload :CompileCache
12
12
  autoload :ComponentError
13
13
  autoload :Deprecation
14
+ autoload :GlobalOutputBuffer
14
15
  autoload :Instrumentation
16
+ autoload :OutputBufferStack
15
17
  autoload :Preview
16
18
  autoload :PreviewTemplateError
17
19
  autoload :TestHelpers
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.51.0
4
+ version: 2.54.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: 2022-03-19 00:00:00.000000000 Z
11
+ date: 2022-05-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -293,6 +293,7 @@ files:
293
293
  - README.md
294
294
  - app/assets/vendor/prism.css
295
295
  - app/assets/vendor/prism.min.js
296
+ - app/controllers/concerns/view_component/preview_actions.rb
296
297
  - app/controllers/view_components_controller.rb
297
298
  - app/helpers/preview_helper.rb
298
299
  - app/views/test_mailer/test_email.html.erb
@@ -333,7 +334,9 @@ files:
333
334
  - lib/view_component/docs_builder_component.html.erb
334
335
  - lib/view_component/docs_builder_component.rb
335
336
  - lib/view_component/engine.rb
337
+ - lib/view_component/global_output_buffer.rb
336
338
  - lib/view_component/instrumentation.rb
339
+ - lib/view_component/output_buffer_stack.rb
337
340
  - lib/view_component/polymorphic_slots.rb
338
341
  - lib/view_component/preview.rb
339
342
  - lib/view_component/preview_template_error.rb