File: slot.rb

package info (click to toggle)
ruby-view-component 4.4.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 472 kB
  • sloc: ruby: 2,278; makefile: 4
file content (118 lines) | stat: -rw-r--r-- 3,636 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# frozen_string_literal: true

require "view_component/with_content_helper"

module ViewComponent
  class Slot
    include ViewComponent::WithContentHelper

    attr_writer :__vc_component_instance, :__vc_content_block, :__vc_content, :__vc_content_block_virtual_path

    def initialize(parent)
      @parent = parent
    end

    def content?
      return true if defined?(@__vc_content) && @__vc_content.present?
      return true if defined?(@__vc_content_set_by_with_content) && @__vc_content_set_by_with_content.present?
      return true if defined?(@__vc_content_block) && @__vc_content_block.present?
      return false if !__vc_component_instance?

      @__vc_component_instance.content?
    end

    def with_content(args)
      if __vc_component_instance?
        @__vc_component_instance.with_content(args)
      else
        super
      end
    end

    # Used to render the slot content in the template
    #
    # There's currently 3 different values that may be set, that we can render.
    #
    # If the slot renderable is a component, the string class name of a
    # component, or a function that returns a component, we render that
    # component instance, returning the string.
    #
    # If the slot renderable is a function and returns a string, it's
    # set as `@__vc_content` and is returned directly.
    #
    # If there is no slot renderable, we evaluate the block passed to
    # the slot and return it.
    def to_s
      return @content if defined?(@content)

      view_context = @parent.send(:view_context)

      if defined?(@__vc_content_block) && defined?(@__vc_content_set_by_with_content)
        raise DuplicateSlotContentError.new(self.class.name)
      end

      @content =
        if __vc_component_instance?
          @__vc_component_instance.__vc_original_view_context = @parent.__vc_original_view_context

          if defined?(@__vc_content_block)
            # render_in is faster than `parent.render`
            @__vc_component_instance.render_in(view_context) do |*args|
              @parent.with_captured_virtual_path(@__vc_content_block_virtual_path) do
                @__vc_content_block.call(*args)
              end
            end
          else
            @__vc_component_instance.render_in(view_context)
          end
        elsif defined?(@__vc_content)
          @__vc_content
        elsif defined?(@__vc_content_block)
          @parent.with_captured_virtual_path(@__vc_content_block_virtual_path) do
            view_context.capture(&@__vc_content_block)
          end
        elsif defined?(@__vc_content_set_by_with_content)
          @__vc_content_set_by_with_content
        end

      @content = @content.to_s
    end

    # Allow access to public component methods via the wrapper
    #
    # for example
    #
    # calling `header.name` (where `header` is a slot) will call `name`
    # on the `HeaderComponent` instance.
    #
    # Where the component may look like:
    #
    # class MyComponent < ViewComponent::Base
    #   has_one :header, HeaderComponent
    #
    #   class HeaderComponent < ViewComponent::Base
    #     def name
    #       @name
    #     end
    #   end
    # end
    #
    def method_missing(symbol, *args, **kwargs, &block)
      @__vc_component_instance.public_send(symbol, *args, **kwargs, &block)
    end

    def html_safe?
      to_s.html_safe?
    end

    def respond_to_missing?(symbol, include_all = false)
      __vc_component_instance? && @__vc_component_instance.respond_to?(symbol, include_all)
    end

    private

    def __vc_component_instance?
      defined?(@__vc_component_instance)
    end
  end
end