view_component 2.49.1 → 3.23.2
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.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/app/assets/vendor/prism.css +3 -195
- data/app/assets/vendor/prism.min.js +11 -11
- data/app/controllers/concerns/view_component/preview_actions.rb +108 -0
- data/app/controllers/view_components_controller.rb +1 -87
- data/app/controllers/view_components_system_test_controller.rb +30 -0
- data/app/helpers/preview_helper.rb +30 -12
- data/app/views/view_components/_preview_source.html.erb +3 -3
- data/app/views/view_components/preview.html.erb +2 -2
- data/docs/CHANGELOG.md +1653 -24
- data/lib/rails/generators/abstract_generator.rb +16 -10
- data/lib/rails/generators/component/component_generator.rb +8 -4
- data/lib/rails/generators/component/templates/component.rb.tt +3 -2
- data/lib/rails/generators/erb/component_generator.rb +1 -1
- data/lib/rails/generators/locale/component_generator.rb +4 -4
- data/lib/rails/generators/preview/component_generator.rb +17 -3
- data/lib/rails/generators/preview/templates/component_preview.rb.tt +5 -1
- data/lib/rails/generators/rspec/component_generator.rb +15 -3
- data/lib/rails/generators/rspec/templates/component_spec.rb.tt +3 -1
- data/lib/rails/generators/stimulus/component_generator.rb +8 -3
- data/lib/rails/generators/stimulus/templates/component_controller.ts.tt +9 -0
- data/lib/rails/generators/test_unit/templates/component_test.rb.tt +3 -1
- data/lib/view_component/base.rb +352 -196
- data/lib/view_component/capture_compatibility.rb +44 -0
- data/lib/view_component/collection.rb +28 -9
- data/lib/view_component/compiler.rb +162 -193
- data/lib/view_component/config.rb +225 -0
- data/lib/view_component/configurable.rb +17 -0
- data/lib/view_component/deprecation.rb +8 -0
- data/lib/view_component/engine.rb +74 -47
- data/lib/view_component/errors.rb +240 -0
- data/lib/view_component/inline_template.rb +55 -0
- data/lib/view_component/instrumentation.rb +10 -2
- data/lib/view_component/preview.rb +21 -19
- data/lib/view_component/rails/tasks/view_component.rake +11 -2
- data/lib/view_component/render_component_helper.rb +1 -0
- data/lib/view_component/render_component_to_string_helper.rb +1 -1
- data/lib/view_component/render_to_string_monkey_patch.rb +1 -1
- data/lib/view_component/rendering_component_helper.rb +1 -1
- data/lib/view_component/rendering_monkey_patch.rb +1 -1
- data/lib/view_component/slot.rb +119 -1
- data/lib/view_component/slotable.rb +393 -96
- data/lib/view_component/slotable_default.rb +20 -0
- data/lib/view_component/system_test_case.rb +13 -0
- data/lib/view_component/system_test_helpers.rb +27 -0
- data/lib/view_component/template.rb +134 -0
- data/lib/view_component/test_helpers.rb +208 -47
- data/lib/view_component/translatable.rb +51 -33
- data/lib/view_component/use_helpers.rb +42 -0
- data/lib/view_component/version.rb +5 -4
- data/lib/view_component/with_content_helper.rb +3 -8
- data/lib/view_component.rb +7 -12
- metadata +339 -57
- data/lib/rails/generators/component/USAGE +0 -13
- data/lib/view_component/content_areas.rb +0 -57
- data/lib/view_component/polymorphic_slots.rb +0 -73
- data/lib/view_component/preview_template_error.rb +0 -6
- data/lib/view_component/previewable.rb +0 -62
- data/lib/view_component/slot_v2.rb +0 -104
- data/lib/view_component/slotable_v2.rb +0 -307
- data/lib/view_component/template_error.rb +0 -9
- data/lib/yard/mattr_accessor_handler.rb +0 -19
@@ -1,57 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "active_support/concern"
|
4
|
-
|
5
|
-
require "view_component/slot"
|
6
|
-
|
7
|
-
# DEPRECATED - ContentAreas is deprecated and will be removed in v3.0.0
|
8
|
-
module ViewComponent
|
9
|
-
module ContentAreas
|
10
|
-
extend ActiveSupport::Concern
|
11
|
-
|
12
|
-
# Assign the provided content to the content area accessor
|
13
|
-
#
|
14
|
-
# @private
|
15
|
-
def with(area, content = nil, &block)
|
16
|
-
unless content_areas.include?(area)
|
17
|
-
raise ArgumentError.new(
|
18
|
-
"Unknown content_area '#{area}' for #{self} - expected one of '#{content_areas}'.\n\n" \
|
19
|
-
"To fix this issue, add `with_content_area :#{area}` to #{self} or reference " \
|
20
|
-
"a valid content area."
|
21
|
-
)
|
22
|
-
end
|
23
|
-
|
24
|
-
if block_given?
|
25
|
-
content = view_context.capture(&block)
|
26
|
-
end
|
27
|
-
|
28
|
-
instance_variable_set("@#{area}".to_sym, content)
|
29
|
-
nil
|
30
|
-
end
|
31
|
-
|
32
|
-
class_methods do
|
33
|
-
def with_content_areas(*areas)
|
34
|
-
ActiveSupport::Deprecation.warn(
|
35
|
-
"`with_content_areas` is deprecated and will be removed in ViewComponent v3.0.0.\n\n" \
|
36
|
-
"Use slots (https://viewcomponent.org/guide/slots.html) instead."
|
37
|
-
)
|
38
|
-
|
39
|
-
if areas.include?(:content)
|
40
|
-
raise ArgumentError.new(
|
41
|
-
"#{self} defines a content area called :content, which is a reserved name. \n\n" \
|
42
|
-
"To fix this issue, use another name, such as `:body`."
|
43
|
-
)
|
44
|
-
end
|
45
|
-
|
46
|
-
areas.each do |area|
|
47
|
-
define_method area.to_sym do
|
48
|
-
content unless content_evaluated? # ensure content is loaded so content_areas will be defined
|
49
|
-
instance_variable_get(:"@#{area}") if instance_variable_defined?(:"@#{area}")
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
self.content_areas = areas
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
@@ -1,73 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ViewComponent
|
4
|
-
module PolymorphicSlots
|
5
|
-
# In older rails versions, using a concern isn't a good idea here because they appear to not work with
|
6
|
-
# Module#prepend and class methods.
|
7
|
-
def self.included(base)
|
8
|
-
base.singleton_class.prepend(ClassMethods)
|
9
|
-
base.include(InstanceMethods)
|
10
|
-
end
|
11
|
-
|
12
|
-
module ClassMethods
|
13
|
-
def renders_one(slot_name, callable = nil)
|
14
|
-
return super unless callable.is_a?(Hash) && callable.key?(:types)
|
15
|
-
|
16
|
-
validate_singular_slot_name(slot_name)
|
17
|
-
register_polymorphic_slot(slot_name, callable[:types], collection: false)
|
18
|
-
end
|
19
|
-
|
20
|
-
def renders_many(slot_name, callable = nil)
|
21
|
-
return super unless callable.is_a?(Hash) && callable.key?(:types)
|
22
|
-
|
23
|
-
validate_plural_slot_name(slot_name)
|
24
|
-
register_polymorphic_slot(slot_name, callable[:types], collection: true)
|
25
|
-
end
|
26
|
-
|
27
|
-
def register_polymorphic_slot(slot_name, types, collection:)
|
28
|
-
renderable_hash = types.each_with_object({}) do |(poly_type, poly_callable), memo|
|
29
|
-
memo[poly_type] = define_slot(
|
30
|
-
"#{slot_name}_#{poly_type}", collection: collection, callable: poly_callable
|
31
|
-
)
|
32
|
-
|
33
|
-
getter_name = slot_name
|
34
|
-
setter_name =
|
35
|
-
if collection
|
36
|
-
"#{ActiveSupport::Inflector.singularize(slot_name)}_#{poly_type}"
|
37
|
-
else
|
38
|
-
"#{slot_name}_#{poly_type}"
|
39
|
-
end
|
40
|
-
|
41
|
-
define_method(getter_name) do
|
42
|
-
get_slot(slot_name)
|
43
|
-
end
|
44
|
-
|
45
|
-
define_method(setter_name) do |*args, &block|
|
46
|
-
set_polymorphic_slot(slot_name, poly_type, *args, &block)
|
47
|
-
end
|
48
|
-
ruby2_keywords(setter_name.to_sym) if respond_to?(:ruby2_keywords, true)
|
49
|
-
end
|
50
|
-
|
51
|
-
self.registered_slots[slot_name] = {
|
52
|
-
collection: collection,
|
53
|
-
renderable_hash: renderable_hash
|
54
|
-
}
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
module InstanceMethods
|
59
|
-
def set_polymorphic_slot(slot_name, poly_type = nil, *args, &block)
|
60
|
-
slot_definition = self.class.registered_slots[slot_name]
|
61
|
-
|
62
|
-
if !slot_definition[:collection] && get_slot(slot_name)
|
63
|
-
raise ArgumentError, "content for slot '#{slot_name}' has already been provided"
|
64
|
-
end
|
65
|
-
|
66
|
-
poly_def = slot_definition[:renderable_hash][poly_type]
|
67
|
-
|
68
|
-
set_slot(slot_name, poly_def, *args, &block)
|
69
|
-
end
|
70
|
-
ruby2_keywords(:set_polymorphic_slot) if respond_to?(:ruby2_keywords, true)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
@@ -1,62 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "active_support/concern"
|
4
|
-
|
5
|
-
module ViewComponent
|
6
|
-
module Previewable
|
7
|
-
extend ActiveSupport::Concern
|
8
|
-
|
9
|
-
included do
|
10
|
-
# Enable or disable component previews:
|
11
|
-
#
|
12
|
-
# config.view_component.show_previews = true
|
13
|
-
#
|
14
|
-
# Defaults to `true` in development.
|
15
|
-
#
|
16
|
-
mattr_accessor :show_previews, instance_writer: false
|
17
|
-
|
18
|
-
# Enable or disable source code previews in component previews:
|
19
|
-
#
|
20
|
-
# config.view_component.show_previews_source = true
|
21
|
-
#
|
22
|
-
# Defaults to `false`.
|
23
|
-
#
|
24
|
-
mattr_accessor :show_previews_source, instance_writer: false, default: false
|
25
|
-
|
26
|
-
# Set a custom default layout used for preview index and individual previews:
|
27
|
-
#
|
28
|
-
# config.view_component.default_preview_layout = "component_preview"
|
29
|
-
#
|
30
|
-
mattr_accessor :default_preview_layout, instance_writer: false
|
31
|
-
|
32
|
-
# Set the location of component previews:
|
33
|
-
#
|
34
|
-
# config.view_component.preview_paths << "#{Rails.root}/lib/component_previews"
|
35
|
-
#
|
36
|
-
mattr_accessor :preview_paths, instance_writer: false
|
37
|
-
|
38
|
-
# @deprecated Use `preview_paths` instead. Will be removed in v3.0.0.
|
39
|
-
mattr_accessor :preview_path, instance_writer: false
|
40
|
-
|
41
|
-
# Set the entry route for component previews:
|
42
|
-
#
|
43
|
-
# config.view_component.preview_route = "/previews"
|
44
|
-
#
|
45
|
-
# Defaults to `/rails/view_components` when `show_previews` is enabled.
|
46
|
-
#
|
47
|
-
mattr_accessor :preview_route, instance_writer: false do
|
48
|
-
"/rails/view_components"
|
49
|
-
end
|
50
|
-
|
51
|
-
# Set the controller used for previewing components:
|
52
|
-
#
|
53
|
-
# config.view_component.preview_controller = "MyPreviewController"
|
54
|
-
#
|
55
|
-
# Defaults to `ViewComponentsController`.
|
56
|
-
#
|
57
|
-
mattr_accessor :preview_controller, instance_writer: false do
|
58
|
-
"ViewComponentsController"
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
@@ -1,104 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "view_component/with_content_helper"
|
4
|
-
|
5
|
-
module ViewComponent
|
6
|
-
class SlotV2
|
7
|
-
include ViewComponent::WithContentHelper
|
8
|
-
|
9
|
-
attr_writer :__vc_component_instance, :__vc_content_block, :__vc_content
|
10
|
-
|
11
|
-
def initialize(parent)
|
12
|
-
@parent = parent
|
13
|
-
end
|
14
|
-
|
15
|
-
# Used to render the slot content in the template
|
16
|
-
#
|
17
|
-
# There's currently 3 different values that may be set, that we can render.
|
18
|
-
#
|
19
|
-
# If the slot renderable is a component, the string class name of a
|
20
|
-
# component, or a function that returns a component, we render that
|
21
|
-
# component instance, returning the string.
|
22
|
-
#
|
23
|
-
# If the slot renderable is a function and returns a string, it's
|
24
|
-
# set as `@__vc_content` and is returned directly.
|
25
|
-
#
|
26
|
-
# If there is no slot renderable, we evaluate the block passed to
|
27
|
-
# the slot and return it.
|
28
|
-
def to_s
|
29
|
-
return @content if defined?(@content)
|
30
|
-
|
31
|
-
view_context = @parent.send(:view_context)
|
32
|
-
|
33
|
-
if defined?(@__vc_content_block) && defined?(@__vc_content_set_by_with_content)
|
34
|
-
raise ArgumentError.new(
|
35
|
-
"It looks like a block was provided after calling `with_content` on #{self.class.name}, " \
|
36
|
-
"which means that ViewComponent doesn't know which content to use.\n\n" \
|
37
|
-
"To fix this issue, use either `with_content` or a block."
|
38
|
-
)
|
39
|
-
end
|
40
|
-
|
41
|
-
@content =
|
42
|
-
if defined?(@__vc_component_instance)
|
43
|
-
@__vc_component_instance.__vc_original_view_context = @parent.__vc_original_view_context
|
44
|
-
|
45
|
-
if defined?(@__vc_content_set_by_with_content)
|
46
|
-
@__vc_component_instance.with_content(@__vc_content_set_by_with_content)
|
47
|
-
|
48
|
-
view_context.capture do
|
49
|
-
@__vc_component_instance.render_in(view_context)
|
50
|
-
end
|
51
|
-
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
|
56
|
-
else
|
57
|
-
view_context.capture do
|
58
|
-
@__vc_component_instance.render_in(view_context)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
elsif defined?(@__vc_content)
|
62
|
-
@__vc_content
|
63
|
-
elsif defined?(@__vc_content_block)
|
64
|
-
view_context.capture(&@__vc_content_block)
|
65
|
-
elsif defined?(@__vc_content_set_by_with_content)
|
66
|
-
@__vc_content_set_by_with_content
|
67
|
-
end
|
68
|
-
|
69
|
-
@content = @content.to_s
|
70
|
-
end
|
71
|
-
|
72
|
-
# Allow access to public component methods via the wrapper
|
73
|
-
#
|
74
|
-
# for example
|
75
|
-
#
|
76
|
-
# calling `header.name` (where `header` is a slot) will call `name`
|
77
|
-
# on the `HeaderComponent` instance.
|
78
|
-
#
|
79
|
-
# Where the component may look like:
|
80
|
-
#
|
81
|
-
# class MyComponent < ViewComponent::Base
|
82
|
-
# has_one :header, HeaderComponent
|
83
|
-
#
|
84
|
-
# class HeaderComponent < ViewComponent::Base
|
85
|
-
# def name
|
86
|
-
# @name
|
87
|
-
# end
|
88
|
-
# end
|
89
|
-
# end
|
90
|
-
#
|
91
|
-
def method_missing(symbol, *args, &block)
|
92
|
-
@__vc_component_instance.public_send(symbol, *args, &block)
|
93
|
-
end
|
94
|
-
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
95
|
-
|
96
|
-
def html_safe?
|
97
|
-
to_s.html_safe?
|
98
|
-
end
|
99
|
-
|
100
|
-
def respond_to_missing?(symbol, include_all = false)
|
101
|
-
defined?(@__vc_component_instance) && @__vc_component_instance.respond_to?(symbol, include_all)
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
@@ -1,307 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "active_support/concern"
|
4
|
-
require "view_component/slot_v2"
|
5
|
-
|
6
|
-
module ViewComponent
|
7
|
-
module SlotableV2
|
8
|
-
extend ActiveSupport::Concern
|
9
|
-
|
10
|
-
# Setup component slot state
|
11
|
-
included do
|
12
|
-
# Hash of registered Slots
|
13
|
-
class_attribute :registered_slots
|
14
|
-
self.registered_slots = {}
|
15
|
-
end
|
16
|
-
|
17
|
-
class_methods do
|
18
|
-
##
|
19
|
-
# Registers a sub-component
|
20
|
-
#
|
21
|
-
# = Example
|
22
|
-
#
|
23
|
-
# renders_one :header -> (classes:) do
|
24
|
-
# HeaderComponent.new(classes: classes)
|
25
|
-
# end
|
26
|
-
#
|
27
|
-
# # OR
|
28
|
-
#
|
29
|
-
# renders_one :header, HeaderComponent
|
30
|
-
#
|
31
|
-
# where `HeaderComponent` is defined as:
|
32
|
-
#
|
33
|
-
# class HeaderComponent < ViewComponent::Base
|
34
|
-
# def initialize(classes:)
|
35
|
-
# @classes = classes
|
36
|
-
# end
|
37
|
-
# end
|
38
|
-
#
|
39
|
-
# and has the following template:
|
40
|
-
#
|
41
|
-
# <header class="<%= @classes %>">
|
42
|
-
# <%= content %>
|
43
|
-
# </header>
|
44
|
-
#
|
45
|
-
# = Rendering sub-component content
|
46
|
-
#
|
47
|
-
# The component's sidecar template can access the sub-component by calling a
|
48
|
-
# helper method with the same name as the sub-component.
|
49
|
-
#
|
50
|
-
# <h1>
|
51
|
-
# <%= header do %>
|
52
|
-
# My header title
|
53
|
-
# <% end %>
|
54
|
-
# </h1>
|
55
|
-
#
|
56
|
-
# = Setting sub-component content
|
57
|
-
#
|
58
|
-
# Consumers of the component can render a sub-component by calling a
|
59
|
-
# helper method with the same name as the slot.
|
60
|
-
#
|
61
|
-
# <%= render_inline(MyComponent.new) do |component| %>
|
62
|
-
# <% component.header(classes: "Foo") do %>
|
63
|
-
# <p>Bar</p>
|
64
|
-
# <% end %>
|
65
|
-
# <% end %>
|
66
|
-
def renders_one(slot_name, callable = nil)
|
67
|
-
validate_singular_slot_name(slot_name)
|
68
|
-
|
69
|
-
define_method slot_name do |*args, &block|
|
70
|
-
if args.empty? && block.nil?
|
71
|
-
get_slot(slot_name)
|
72
|
-
else
|
73
|
-
set_slot(slot_name, nil, *args, &block)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
ruby2_keywords(slot_name.to_sym) if respond_to?(:ruby2_keywords, true)
|
77
|
-
|
78
|
-
register_slot(slot_name, collection: false, callable: callable)
|
79
|
-
end
|
80
|
-
|
81
|
-
##
|
82
|
-
# Registers a collection sub-component
|
83
|
-
#
|
84
|
-
# = Example
|
85
|
-
#
|
86
|
-
# render_many :items, -> (name:) { ItemComponent.new(name: name }
|
87
|
-
#
|
88
|
-
# # OR
|
89
|
-
#
|
90
|
-
# render_many :items, ItemComponent
|
91
|
-
#
|
92
|
-
# = Rendering sub-components
|
93
|
-
#
|
94
|
-
# The component's sidecar template can access the slot by calling a
|
95
|
-
# helper method with the same name as the slot.
|
96
|
-
#
|
97
|
-
# <h1>
|
98
|
-
# <% items.each do |item| %>
|
99
|
-
# <%= item %>
|
100
|
-
# <% end %>
|
101
|
-
# </h1>
|
102
|
-
#
|
103
|
-
# = Setting sub-component content
|
104
|
-
#
|
105
|
-
# Consumers of the component can set the content of a slot by calling a
|
106
|
-
# helper method with the same name as the slot. The method can be
|
107
|
-
# called multiple times to append to the slot.
|
108
|
-
#
|
109
|
-
# <%= render_inline(MyComponent.new) do |component| %>
|
110
|
-
# <% component.item(name: "Foo") do %>
|
111
|
-
# <p>One</p>
|
112
|
-
# <% end %>
|
113
|
-
#
|
114
|
-
# <% component.item(name: "Bar") do %>
|
115
|
-
# <p>two</p>
|
116
|
-
# <% end %>
|
117
|
-
# <% end %>
|
118
|
-
def renders_many(slot_name, callable = nil)
|
119
|
-
validate_plural_slot_name(slot_name)
|
120
|
-
|
121
|
-
singular_name = ActiveSupport::Inflector.singularize(slot_name)
|
122
|
-
|
123
|
-
# Define setter for singular names
|
124
|
-
# for example `renders_many :items` allows fetching all tabs with
|
125
|
-
# `component.tabs` and setting a tab with `component.tab`
|
126
|
-
define_method singular_name do |*args, &block|
|
127
|
-
set_slot(slot_name, nil, *args, &block)
|
128
|
-
end
|
129
|
-
ruby2_keywords(singular_name.to_sym) if respond_to?(:ruby2_keywords, true)
|
130
|
-
|
131
|
-
# Instantiates and and adds multiple slots forwarding the first
|
132
|
-
# argument to each slot constructor
|
133
|
-
define_method slot_name do |collection_args = nil, &block|
|
134
|
-
if collection_args.nil? && block.nil?
|
135
|
-
get_slot(slot_name)
|
136
|
-
else
|
137
|
-
collection_args.map do |args|
|
138
|
-
set_slot(slot_name, nil, **args, &block)
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
register_slot(slot_name, collection: true, callable: callable)
|
144
|
-
end
|
145
|
-
|
146
|
-
def slot_type(slot_name)
|
147
|
-
registered_slot = registered_slots[slot_name]
|
148
|
-
if registered_slot
|
149
|
-
registered_slot[:collection] ? :collection : :single
|
150
|
-
else
|
151
|
-
plural_slot_name = ActiveSupport::Inflector.pluralize(slot_name).to_sym
|
152
|
-
plural_registered_slot = registered_slots[plural_slot_name]
|
153
|
-
plural_registered_slot&.fetch(:collection) ? :collection_item : nil
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
# Clone slot configuration into child class
|
158
|
-
# see #test_slots_pollution
|
159
|
-
def inherited(child)
|
160
|
-
child.registered_slots = self.registered_slots.clone
|
161
|
-
super
|
162
|
-
end
|
163
|
-
|
164
|
-
private
|
165
|
-
|
166
|
-
def register_slot(slot_name, **kwargs)
|
167
|
-
self.registered_slots[slot_name] = define_slot(slot_name, **kwargs)
|
168
|
-
end
|
169
|
-
|
170
|
-
def define_slot(slot_name, collection:, callable:)
|
171
|
-
# Setup basic slot data
|
172
|
-
slot = {
|
173
|
-
collection: collection,
|
174
|
-
}
|
175
|
-
return slot unless callable
|
176
|
-
|
177
|
-
# If callable responds to `render_in`, we set it on the slot as a renderable
|
178
|
-
if callable.respond_to?(:method_defined?) && callable.method_defined?(:render_in)
|
179
|
-
slot[:renderable] = callable
|
180
|
-
elsif callable.is_a?(String)
|
181
|
-
# If callable is a string, we assume it's referencing an internal class
|
182
|
-
slot[:renderable_class_name] = callable
|
183
|
-
elsif callable.respond_to?(:call)
|
184
|
-
# If slot doesn't respond to `render_in`, we assume it's a proc,
|
185
|
-
# define a method, and save a reference to it to call when setting
|
186
|
-
method_name = :"_call_#{slot_name}"
|
187
|
-
define_method method_name, &callable
|
188
|
-
slot[:renderable_function] = instance_method(method_name)
|
189
|
-
else
|
190
|
-
raise(
|
191
|
-
ArgumentError,
|
192
|
-
"invalid slot definition. Please pass a class, string, or callable (i.e. proc, lambda, etc)"
|
193
|
-
)
|
194
|
-
end
|
195
|
-
|
196
|
-
slot
|
197
|
-
end
|
198
|
-
|
199
|
-
def validate_plural_slot_name(slot_name)
|
200
|
-
if slot_name.to_sym == :contents
|
201
|
-
raise ArgumentError.new(
|
202
|
-
"#{self} declares a slot named #{slot_name}, which is a reserved word in the ViewComponent framework.\n\n" \
|
203
|
-
"To fix this issue, choose a different name."
|
204
|
-
)
|
205
|
-
end
|
206
|
-
|
207
|
-
raise_if_slot_registered(slot_name)
|
208
|
-
end
|
209
|
-
|
210
|
-
def validate_singular_slot_name(slot_name)
|
211
|
-
if slot_name.to_sym == :content
|
212
|
-
raise ArgumentError.new(
|
213
|
-
"#{self} declares a slot named #{slot_name}, which is a reserved word in the ViewComponent framework.\n\n" \
|
214
|
-
"To fix this issue, choose a different name."
|
215
|
-
)
|
216
|
-
end
|
217
|
-
|
218
|
-
raise_if_slot_registered(slot_name)
|
219
|
-
end
|
220
|
-
|
221
|
-
def raise_if_slot_registered(slot_name)
|
222
|
-
if self.registered_slots.key?(slot_name)
|
223
|
-
# TODO remove? This breaks overriding slots when slots are inherited
|
224
|
-
raise ArgumentError.new(
|
225
|
-
"#{self} declares the #{slot_name} slot multiple times.\n\n" \
|
226
|
-
"To fix this issue, choose a different slot name."
|
227
|
-
)
|
228
|
-
end
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
def get_slot(slot_name)
|
233
|
-
content unless content_evaluated? # ensure content is loaded so slots will be defined
|
234
|
-
|
235
|
-
slot = self.class.registered_slots[slot_name]
|
236
|
-
@__vc_set_slots ||= {}
|
237
|
-
|
238
|
-
if @__vc_set_slots[slot_name]
|
239
|
-
return @__vc_set_slots[slot_name]
|
240
|
-
end
|
241
|
-
|
242
|
-
if slot[:collection]
|
243
|
-
[]
|
244
|
-
else
|
245
|
-
nil
|
246
|
-
end
|
247
|
-
end
|
248
|
-
|
249
|
-
def set_slot(slot_name, slot_definition = nil, *args, &block)
|
250
|
-
slot_definition ||= self.class.registered_slots[slot_name]
|
251
|
-
slot = SlotV2.new(self)
|
252
|
-
|
253
|
-
# Passing the block to the sub-component wrapper like this has two
|
254
|
-
# benefits:
|
255
|
-
#
|
256
|
-
# 1. If this is a `content_area` style sub-component, we will render the
|
257
|
-
# block via the `slot`
|
258
|
-
#
|
259
|
-
# 2. Since we've to pass block content to components when calling
|
260
|
-
# `render`, evaluating the block here would require us to call
|
261
|
-
# `view_context.capture` twice, which is slower
|
262
|
-
slot.__vc_content_block = block if block_given?
|
263
|
-
|
264
|
-
# If class
|
265
|
-
if slot_definition[:renderable]
|
266
|
-
slot.__vc_component_instance = slot_definition[:renderable].new(*args)
|
267
|
-
# If class name as a string
|
268
|
-
elsif slot_definition[:renderable_class_name]
|
269
|
-
slot.__vc_component_instance =
|
270
|
-
self.class.const_get(slot_definition[:renderable_class_name]).new(*args)
|
271
|
-
# If passed a lambda
|
272
|
-
elsif slot_definition[:renderable_function]
|
273
|
-
# Use `bind(self)` to ensure lambda is executed in the context of the
|
274
|
-
# current component. This is necessary to allow the lambda to access helper
|
275
|
-
# methods like `content_tag` as well as parent component state.
|
276
|
-
renderable_function = slot_definition[:renderable_function].bind(self)
|
277
|
-
renderable_value =
|
278
|
-
if block_given?
|
279
|
-
renderable_function.call(*args) do |*args|
|
280
|
-
view_context.capture(*args, &block)
|
281
|
-
end
|
282
|
-
else
|
283
|
-
renderable_function.call(*args)
|
284
|
-
end
|
285
|
-
|
286
|
-
# Function calls can return components, so if it's a component handle it specially
|
287
|
-
if renderable_value.respond_to?(:render_in)
|
288
|
-
slot.__vc_component_instance = renderable_value
|
289
|
-
else
|
290
|
-
slot.__vc_content = renderable_value
|
291
|
-
end
|
292
|
-
end
|
293
|
-
|
294
|
-
@__vc_set_slots ||= {}
|
295
|
-
|
296
|
-
if slot_definition[:collection]
|
297
|
-
@__vc_set_slots[slot_name] ||= []
|
298
|
-
@__vc_set_slots[slot_name].push(slot)
|
299
|
-
else
|
300
|
-
@__vc_set_slots[slot_name] = slot
|
301
|
-
end
|
302
|
-
|
303
|
-
slot
|
304
|
-
end
|
305
|
-
ruby2_keywords(:set_slot) if respond_to?(:ruby2_keywords, true)
|
306
|
-
end
|
307
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module YARD
|
4
|
-
# YARD Handler to parse `mattr_accessor` calls.
|
5
|
-
class MattrAccessorHandler < YARD::Handlers::Ruby::Base
|
6
|
-
handles method_call(:mattr_accessor)
|
7
|
-
namespace_only
|
8
|
-
|
9
|
-
process do
|
10
|
-
name = statement.parameters.first.jump(:tstring_content, :ident).source
|
11
|
-
object = YARD::CodeObjects::MethodObject.new(namespace, name)
|
12
|
-
register(object)
|
13
|
-
parse_block(statement.last, owner: object)
|
14
|
-
|
15
|
-
object.dynamic = true
|
16
|
-
object[:mattr_accessor] = true
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|