module ViewComponent::Slotable
def set_slot(slot_name, slot_definition = nil, *args, &block)
def set_slot(slot_name, slot_definition = nil, *args, &block) slot_definition ||= self.class.registered_slots[slot_name] slot = Slot.new(self) # Passing the block to the sub-component wrapper like this has two # benefits: # # 1. If this is a `content_area` style sub-component, we will render the # block via the `slot` # # 2. Since we have to pass block content to components when calling # `render`, evaluating the block here would require us to call # `view_context.capture` twice, which is slower slot.__vc_content_block = block if block # If class if slot_definition[:renderable] slot.__vc_component_instance = slot_definition[:renderable].new(*args) # If class name as a string elsif slot_definition[:renderable_class_name] slot.__vc_component_instance = self.class.const_get(slot_definition[:renderable_class_name]).new(*args) # If passed a lambda elsif slot_definition[:renderable_function] # Use `bind(self)` to ensure lambda is executed in the context of the # current component. This is necessary to allow the lambda to access helper # methods like `content_tag` as well as parent component state. renderable_function = slot_definition[:renderable_function].bind(self) renderable_value = if block renderable_function.call(*args) do |*rargs| view_context.capture(*rargs, &block) end else renderable_function.call(*args) end # Function calls can return components, so if it's a component handle it specially if renderable_value.respond_to?(:render_in) slot.__vc_component_instance = renderable_value else slot.__vc_content = renderable_value end end @__vc_set_slots ||= {} if slot_definition[:collection] @__vc_set_slots[slot_name] ||= [] @__vc_set_slots[slot_name].push(slot) else @__vc_set_slots[slot_name] = slot end slot end