Skip to content

Commit 9809b89

Browse files
committed
Add configuration to treat content as a slot
Like discussed many times before, this finally adds content as a slot to ViewComponent so that we can begin separating the block provided when rendering from content for performance and ergonomic reasons. This adds two new methods: * `content_is_a_slot!` - This makes the component _and its descendants_ treat `content` as a slot, and the block provided to render calls _is always executed_. * `do_not_use_content_as_a_slot!` - This keeps the existing behavior where `content` is lazily evaluated and is used to return content. There's likely more implications from this change that need to be walked through (like how slots are used, and easily setting content) before this can be merged.
1 parent 7f3213a commit 9809b89

6 files changed

Lines changed: 102 additions & 7 deletions

File tree

lib/view_component/base.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,13 @@ def render_in(view_context, &block)
135135
@__vc_content_evaluated = false
136136
@__vc_render_in_block = block
137137

138+
if self.class.send(:__vc_content_is_a_slot?)
139+
@__vc_content_evaluated = true
140+
if __vc_render_in_block_provided?
141+
view_context.capture(self, &@__vc_render_in_block)
142+
end
143+
end
144+
138145
before_render
139146

140147
if render?
@@ -565,6 +572,10 @@ def render_template_for(requested_details)
565572
child.instance_variable_set(:@__vc_ancestor_calls, vc_ancestor_calls)
566573
end
567574

575+
if defined?(@__vc_content_is_a_slot)
576+
child.instance_variable_set(:@__vc_content_is_a_slot, @__vc_content_is_a_slot)
577+
end
578+
568579
super
569580
end
570581

@@ -687,6 +698,19 @@ def __vc_initialize_parameters
687698
def __vc_provided_collection_parameter
688699
@__vc_provided_collection_parameter ||= nil
689700
end
701+
702+
def __vc_content_is_a_slot?
703+
defined?(@__vc_content_is_a_slot) && @__vc_content_is_a_slot
704+
end
705+
706+
def content_is_a_slot!
707+
@__vc_content_is_a_slot = true
708+
renders_one :content
709+
end
710+
711+
def do_not_use_content_as_a_slot!
712+
@__vc_content_is_a_slot = false
713+
end
690714
end
691715

692716
ActiveSupport.run_load_hooks(:view_component, self)

lib/view_component/slotable.rb

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -304,8 +304,10 @@ def __vc_define_slot(slot_name, collection:, callable:)
304304
end
305305

306306
def __vc_validate_plural_slot_name(slot_name)
307-
if RESERVED_NAMES[:plural].include?(slot_name.to_sym)
308-
raise ReservedPluralSlotNameError.new(name, slot_name)
307+
if slot_name.to_sym == :contents && !__vc_content_is_a_slot?
308+
if RESERVED_NAMES[:plural].include?(slot_name.to_sym)
309+
raise ReservedPluralSlotNameError.new(name, slot_name)
310+
end
309311
end
310312

311313
__vc_raise_if_slot_name_uncountable(slot_name)
@@ -315,12 +317,12 @@ def __vc_validate_plural_slot_name(slot_name)
315317
end
316318

317319
def __vc_validate_singular_slot_name(slot_name)
318-
if slot_name.to_sym == :content
320+
if slot_name.to_sym == :content && !__vc_content_is_a_slot?
319321
raise ContentSlotNameError.new(name)
320-
end
321-
322-
if RESERVED_NAMES[:singular].include?(slot_name.to_sym)
323-
raise ReservedSingularSlotNameError.new(name, slot_name)
322+
elsif !__vc_content_is_a_slot?
323+
if RESERVED_NAMES[:singular].include?(slot_name.to_sym)
324+
raise ReservedSingularSlotNameError.new(name, slot_name)
325+
end
324326
end
325327

326328
__vc_raise_if_slot_conflicts_with_call(slot_name)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<div>
2+
<% if content? %>
3+
<%= content %>
4+
<% end %>
5+
</div>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class ContentAsSlotComponent < ViewComponent::Base
2+
content_is_a_slot!
3+
end
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class ContentNotASlotComponent < ViewComponent::Base
2+
do_not_use_content_as_a_slot!
3+
end

test/sandbox/test/slotable_test.rb

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -831,4 +831,62 @@ def test_allows_marking_slot_as_last
831831

832832
assert_selector(".breadcrumb.active")
833833
end
834+
835+
def test_content_as_a_slot_component
836+
component = ContentAsSlotComponent.new
837+
render_inline(component) do |c|
838+
c.with_content do
839+
"The truth is out there"
840+
end
841+
end
842+
843+
assert component.content?
844+
assert_selector "div", text: "The truth is out there"
845+
end
846+
847+
def test_content_as_a_slot_component_with_content
848+
component = ContentAsSlotComponent.new
849+
component.with_content do
850+
"The truth is out there"
851+
end
852+
render_inline(component)
853+
854+
assert component.content?
855+
assert_selector "div", text: "The truth is out there"
856+
end
857+
858+
def test_content_as_a_slot_inheritance
859+
new_component_class = Class.new(ContentAsSlotComponent)
860+
assert new_component_class.send(:__vc_content_is_a_slot?)
861+
end
862+
863+
def test_content_is_not_a_slot
864+
new_component_class = Class.new(SlotsComponent) do
865+
do_not_use_content_as_a_slot!
866+
end
867+
refute new_component_class.send(:__vc_content_is_a_slot?)
868+
869+
render_inline(SlotsComponent.new) do |component|
870+
component.with_title do
871+
"This is my title!"
872+
end
873+
874+
component.with_subtitle do
875+
"This is my subtitle!"
876+
end
877+
878+
component.with_footer do
879+
"This is the footer"
880+
end
881+
end
882+
883+
assert_text "No tabs provided"
884+
assert_text "No items provided"
885+
end
886+
887+
def test_content_is_not_a_slot_inheritance
888+
refute ContentNotASlotComponent.send(:__vc_content_is_a_slot?)
889+
new_component_class = Class.new(ContentNotASlotComponent)
890+
refute new_component_class.send(:__vc_content_is_a_slot?)
891+
end
834892
end

0 commit comments

Comments
 (0)