view_component 2.51.0 → 2.54.0

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