view_component 3.23.2 → 4.0.1
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/app/controllers/concerns/view_component/preview_actions.rb +11 -14
- data/app/controllers/view_components_system_test_controller.rb +15 -20
- data/app/views/test_mailer/test_asset_email.html.erb +1 -0
- data/app/views/test_mailer/test_url_email.html.erb +1 -0
- data/app/views/view_components/preview.html.erb +1 -9
- data/docs/CHANGELOG.md +415 -0
- data/lib/{rails/generators → generators/view_component}/abstract_generator.rb +2 -2
- data/lib/{rails/generators → generators/view_component}/component/component_generator.rb +16 -3
- data/lib/{rails/generators → generators/view_component}/component/templates/component.rb.tt +6 -1
- data/lib/{rails/generators/erb/component_generator.rb → generators/view_component/erb/erb_generator.rb} +4 -3
- data/lib/{rails/generators/haml/component_generator.rb → generators/view_component/haml/haml_generator.rb} +3 -3
- data/lib/{rails/generators/locale/component_generator.rb → generators/view_component/locale/locale_generator.rb} +3 -3
- data/lib/{rails/generators/preview/component_generator.rb → generators/view_component/preview/preview_generator.rb} +3 -3
- data/lib/{rails/generators/rspec/component_generator.rb → generators/view_component/rspec/rspec_generator.rb} +3 -3
- data/lib/{rails/generators/slim/component_generator.rb → generators/view_component/slim/slim_generator.rb} +3 -3
- data/lib/{rails/generators/stimulus/component_generator.rb → generators/view_component/stimulus/stimulus_generator.rb} +3 -3
- data/lib/generators/view_component/tailwindcss/tailwindcss_generator.rb +11 -0
- data/lib/{rails/generators/test_unit/component_generator.rb → generators/view_component/test_unit/test_unit_generator.rb} +2 -2
- data/lib/view_component/base.rb +160 -155
- data/lib/view_component/collection.rb +19 -25
- data/lib/view_component/compiler.rb +52 -79
- data/lib/view_component/config.rb +51 -85
- data/lib/view_component/configurable.rb +1 -1
- data/lib/view_component/deprecation.rb +1 -1
- data/lib/view_component/engine.rb +37 -107
- data/lib/view_component/errors.rb +16 -34
- data/lib/view_component/inline_template.rb +3 -4
- data/lib/view_component/instrumentation.rb +4 -10
- data/lib/view_component/preview.rb +4 -11
- data/lib/view_component/request_details.rb +30 -0
- data/lib/view_component/slot.rb +6 -13
- data/lib/view_component/slotable.rb +82 -77
- data/lib/view_component/system_spec_helpers.rb +11 -0
- data/lib/view_component/system_test_helpers.rb +1 -2
- data/lib/view_component/template.rb +106 -83
- data/lib/view_component/test_helpers.rb +37 -44
- data/lib/view_component/translatable.rb +33 -32
- data/lib/view_component/version.rb +3 -3
- data/lib/view_component.rb +8 -6
- metadata +31 -559
- data/app/assets/vendor/prism.css +0 -4
- data/app/assets/vendor/prism.min.js +0 -12
- data/app/helpers/preview_helper.rb +0 -85
- data/app/views/view_components/_preview_source.html.erb +0 -17
- data/lib/rails/generators/tailwindcss/component_generator.rb +0 -11
- data/lib/view_component/capture_compatibility.rb +0 -44
- data/lib/view_component/component_error.rb +0 -6
- data/lib/view_component/rails/tasks/view_component.rake +0 -20
- data/lib/view_component/render_component_helper.rb +0 -10
- data/lib/view_component/render_component_to_string_helper.rb +0 -9
- data/lib/view_component/render_monkey_patch.rb +0 -13
- data/lib/view_component/render_to_string_monkey_patch.rb +0 -13
- data/lib/view_component/rendering_component_helper.rb +0 -9
- data/lib/view_component/rendering_monkey_patch.rb +0 -13
- data/lib/view_component/slotable_default.rb +0 -20
- data/lib/view_component/use_helpers.rb +0 -42
- /data/lib/{rails/generators → generators/view_component}/erb/templates/component.html.erb.tt +0 -0
- /data/lib/{rails/generators → generators/view_component}/haml/templates/component.html.haml.tt +0 -0
- /data/lib/{rails/generators → generators/view_component}/preview/templates/component_preview.rb.tt +0 -0
- /data/lib/{rails/generators → generators/view_component}/rspec/templates/component_spec.rb.tt +0 -0
- /data/lib/{rails/generators → generators/view_component}/slim/templates/component.html.slim.tt +0 -0
- /data/lib/{rails/generators → generators/view_component}/stimulus/templates/component_controller.js.tt +0 -0
- /data/lib/{rails/generators → generators/view_component}/stimulus/templates/component_controller.ts.tt +0 -0
- /data/lib/{rails/generators → generators/view_component}/tailwindcss/templates/component.html.erb.tt +0 -0
- /data/lib/{rails/generators → generators/view_component}/test_unit/templates/component_test.rb.tt +0 -0
@@ -38,6 +38,22 @@ module ViewComponent
|
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
+
class MissingTemplateError < StandardError
|
42
|
+
MESSAGE =
|
43
|
+
"No templates for COMPONENT match the request DETAIL.\n\n" \
|
44
|
+
"To fix this issue, provide a suitable template."
|
45
|
+
|
46
|
+
def initialize(component, request_detail)
|
47
|
+
detail = {
|
48
|
+
locale: request_detail.locale,
|
49
|
+
formats: request_detail.formats,
|
50
|
+
variants: request_detail.variants,
|
51
|
+
handlers: request_detail.handlers
|
52
|
+
}
|
53
|
+
super(MESSAGE.gsub("COMPONENT", component).gsub("DETAIL", detail.inspect))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
41
57
|
class DuplicateContentError < StandardError
|
42
58
|
MESSAGE =
|
43
59
|
"It looks like a block was provided after calling `with_content` on COMPONENT, " \
|
@@ -49,18 +65,6 @@ module ViewComponent
|
|
49
65
|
end
|
50
66
|
end
|
51
67
|
|
52
|
-
class EmptyOrInvalidInitializerError < StandardError
|
53
|
-
MESSAGE =
|
54
|
-
"The COMPONENT initializer is empty or invalid. " \
|
55
|
-
"It must accept the parameter `PARAMETER` to render it as a collection.\n\n" \
|
56
|
-
"To fix this issue, update the initializer to accept `PARAMETER`.\n\n" \
|
57
|
-
"See [the collections docs](https://viewcomponent.org/guide/collections.html) for more information on rendering collections."
|
58
|
-
|
59
|
-
def initialize(klass_name, parameter)
|
60
|
-
super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("PARAMETER", parameter.to_s))
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
68
|
class MissingCollectionArgumentError < StandardError
|
65
69
|
MESSAGE =
|
66
70
|
"The initializer for COMPONENT doesn't accept the parameter `PARAMETER`, " \
|
@@ -202,28 +206,6 @@ module ViewComponent
|
|
202
206
|
"`#controller` to a [`#before_render` method](https://viewcomponent.org/api.html#before_render--void)."
|
203
207
|
end
|
204
208
|
|
205
|
-
# :nocov:
|
206
|
-
class NoMatchingTemplatesForPreviewError < StandardError
|
207
|
-
MESSAGE = "Found 0 matches for templates for TEMPLATE_IDENTIFIER."
|
208
|
-
|
209
|
-
def initialize(template_identifier)
|
210
|
-
super(MESSAGE.gsub("TEMPLATE_IDENTIFIER", template_identifier))
|
211
|
-
end
|
212
|
-
end
|
213
|
-
|
214
|
-
class MultipleMatchingTemplatesForPreviewError < StandardError
|
215
|
-
MESSAGE = "Found multiple templates for TEMPLATE_IDENTIFIER."
|
216
|
-
|
217
|
-
def initialize(template_identifier)
|
218
|
-
super(MESSAGE.gsub("TEMPLATE_IDENTIFIER", template_identifier))
|
219
|
-
end
|
220
|
-
end
|
221
|
-
# :nocov:
|
222
|
-
|
223
|
-
class SystemTestControllerOnlyAllowedInTestError < BaseError
|
224
|
-
MESSAGE = "ViewComponent SystemTest controller must only be called in a test environment for security reasons."
|
225
|
-
end
|
226
|
-
|
227
209
|
class SystemTestControllerNefariousPathError < BaseError
|
228
210
|
MESSAGE = "ViewComponent SystemTest controller attempted to load a file outside of the expected directory."
|
229
211
|
end
|
@@ -32,23 +32,22 @@ module ViewComponent # :nodoc:
|
|
32
32
|
|
33
33
|
@__vc_inline_template_defined = true
|
34
34
|
end
|
35
|
-
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
36
35
|
|
37
36
|
def respond_to_missing?(method, include_all = false)
|
38
37
|
method.end_with?("_template") || super
|
39
38
|
end
|
40
39
|
|
41
|
-
def
|
40
|
+
def __vc_inline_template
|
42
41
|
@__vc_inline_template if defined?(@__vc_inline_template)
|
43
42
|
end
|
44
43
|
|
45
|
-
def
|
44
|
+
def __vc_inline_template_language
|
46
45
|
@__vc_inline_template_language if defined?(@__vc_inline_template_language)
|
47
46
|
end
|
48
47
|
|
49
48
|
def inherited(subclass)
|
50
49
|
super
|
51
|
-
subclass.instance_variable_set(:@__vc_inline_template_language,
|
50
|
+
subclass.instance_variable_set(:@__vc_inline_template_language, __vc_inline_template_language)
|
52
51
|
end
|
53
52
|
end
|
54
53
|
end
|
@@ -5,12 +5,14 @@ require "active_support/notifications"
|
|
5
5
|
module ViewComponent # :nodoc:
|
6
6
|
module Instrumentation
|
7
7
|
def self.included(mod)
|
8
|
-
mod.prepend(self) unless
|
8
|
+
mod.prepend(self) unless self <= ViewComponent::Instrumentation
|
9
9
|
end
|
10
10
|
|
11
11
|
def render_in(view_context, &block)
|
12
|
+
return super if !Rails.application.config.view_component.instrumentation_enabled.present?
|
13
|
+
|
12
14
|
ActiveSupport::Notifications.instrument(
|
13
|
-
|
15
|
+
"render.view_component",
|
14
16
|
{
|
15
17
|
name: self.class.name,
|
16
18
|
identifier: self.class.identifier
|
@@ -19,13 +21,5 @@ module ViewComponent # :nodoc:
|
|
19
21
|
super
|
20
22
|
end
|
21
23
|
end
|
22
|
-
|
23
|
-
private
|
24
|
-
|
25
|
-
def notification_name
|
26
|
-
return "!render.view_component" if ViewComponent::Base.config.use_deprecated_instrumentation_name
|
27
|
-
|
28
|
-
"render.view_component"
|
29
|
-
end
|
30
24
|
end
|
31
25
|
end
|
@@ -30,12 +30,10 @@ module ViewComponent # :nodoc:
|
|
30
30
|
}
|
31
31
|
end
|
32
32
|
|
33
|
-
alias_method :render_component, :render
|
34
|
-
|
35
33
|
class << self
|
36
34
|
# Returns all component preview classes.
|
37
35
|
def all
|
38
|
-
|
36
|
+
__vc_load_previews
|
39
37
|
|
40
38
|
descendants
|
41
39
|
end
|
@@ -94,13 +92,8 @@ module ViewComponent # :nodoc:
|
|
94
92
|
.sub(/\..*$/, "")
|
95
93
|
end
|
96
94
|
|
97
|
-
#
|
98
|
-
def
|
99
|
-
source = instance_method(example.to_sym).source.split("\n")
|
100
|
-
source[1...(source.size - 1)].join("\n")
|
101
|
-
end
|
102
|
-
|
103
|
-
def load_previews
|
95
|
+
# @private
|
96
|
+
def __vc_load_previews
|
104
97
|
Array(preview_paths).each do |preview_path|
|
105
98
|
Dir["#{preview_path}/**/*preview.rb"].sort.each { |file| require_dependency file }
|
106
99
|
end
|
@@ -109,7 +102,7 @@ module ViewComponent # :nodoc:
|
|
109
102
|
private
|
110
103
|
|
111
104
|
def preview_paths
|
112
|
-
Base.
|
105
|
+
Base.previews.paths
|
113
106
|
end
|
114
107
|
end
|
115
108
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
# LookupContext computes and encapsulates @details for each request
|
5
|
+
# so that it doesn't need to be recomputed on each partial render.
|
6
|
+
# This data is wrapped in ActionView::TemplateDetails::Requested and
|
7
|
+
# used by instances of ActionView::Resolver to choose which template
|
8
|
+
# best matches the request.
|
9
|
+
#
|
10
|
+
# ActionView considers this logic internal to template/partial resolution.
|
11
|
+
# We're exposing it to the compiler via `refine` so that ViewComponent
|
12
|
+
# can match Rails' template picking logic.
|
13
|
+
module RequestDetails
|
14
|
+
refine ActionView::LookupContext do
|
15
|
+
# Return an abstraction for matching and sorting available templates
|
16
|
+
# based on the current lookup context details.
|
17
|
+
#
|
18
|
+
# @return ActionView::TemplateDetails::Requested
|
19
|
+
# @see ActionView::LookupContext#detail_args_for
|
20
|
+
# @see ActionView::FileSystemResolver#_find_all
|
21
|
+
def vc_requested_details(user_details = {})
|
22
|
+
# The hash `user_details` would normally be the standard arguments that
|
23
|
+
# `render` accepts, but there's currently no mechanism for users to
|
24
|
+
# provide these when calling render on a ViewComponent.
|
25
|
+
details, cached = detail_args_for(user_details)
|
26
|
+
cached || ActionView::TemplateDetails::Requested.new(**details)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/view_component/slot.rb
CHANGED
@@ -58,13 +58,7 @@ module ViewComponent
|
|
58
58
|
if defined?(@__vc_content_block)
|
59
59
|
# render_in is faster than `parent.render`
|
60
60
|
@__vc_component_instance.render_in(view_context) do |*args|
|
61
|
-
|
62
|
-
|
63
|
-
block_context = @__vc_content_block.binding.receiver
|
64
|
-
|
65
|
-
if block_context.class < ActionView::Base
|
66
|
-
block_context.capture(*args, &@__vc_content_block)
|
67
|
-
else
|
61
|
+
@parent.with_original_virtual_path do
|
68
62
|
@__vc_content_block.call(*args)
|
69
63
|
end
|
70
64
|
end
|
@@ -74,7 +68,9 @@ module ViewComponent
|
|
74
68
|
elsif defined?(@__vc_content)
|
75
69
|
@__vc_content
|
76
70
|
elsif defined?(@__vc_content_block)
|
77
|
-
|
71
|
+
@parent.with_original_virtual_path do
|
72
|
+
view_context.capture(&@__vc_content_block)
|
73
|
+
end
|
78
74
|
elsif defined?(@__vc_content_set_by_with_content)
|
79
75
|
@__vc_content_set_by_with_content
|
80
76
|
end
|
@@ -101,15 +97,12 @@ module ViewComponent
|
|
101
97
|
# end
|
102
98
|
# end
|
103
99
|
#
|
104
|
-
def method_missing(symbol, *args, &block)
|
105
|
-
@__vc_component_instance.public_send(symbol, *args, &block)
|
100
|
+
def method_missing(symbol, *args, **kwargs, &block)
|
101
|
+
@__vc_component_instance.public_send(symbol, *args, **kwargs, &block)
|
106
102
|
end
|
107
|
-
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
108
103
|
|
109
104
|
def html_safe?
|
110
|
-
# :nocov:
|
111
105
|
to_s.html_safe?
|
112
|
-
# :nocov:
|
113
106
|
end
|
114
107
|
|
115
108
|
def respond_to_missing?(symbol, include_all = false)
|
@@ -12,12 +12,10 @@ module ViewComponent
|
|
12
12
|
singular: %i[content render].freeze,
|
13
13
|
plural: %i[contents renders].freeze
|
14
14
|
}.freeze
|
15
|
+
private_constant :RESERVED_NAMES
|
15
16
|
|
16
|
-
# Setup component slot state
|
17
17
|
included do
|
18
|
-
|
19
|
-
class_attribute :registered_slots
|
20
|
-
self.registered_slots = {}
|
18
|
+
class_attribute :registered_slots, default: {}
|
21
19
|
end
|
22
20
|
|
23
21
|
class_methods do
|
@@ -75,26 +73,25 @@ module ViewComponent
|
|
75
73
|
#
|
76
74
|
# <%= render_inline(MyComponent.new.with_header_content("Foo")) %>
|
77
75
|
def renders_one(slot_name, callable = nil)
|
78
|
-
|
76
|
+
__vc_validate_singular_slot_name(slot_name)
|
79
77
|
|
80
78
|
if callable.is_a?(Hash) && callable.key?(:types)
|
81
|
-
|
79
|
+
__vc_register_polymorphic_slot(slot_name, callable[:types], collection: false)
|
82
80
|
else
|
83
|
-
|
81
|
+
__vc_validate_plural_slot_name(ActiveSupport::Inflector.pluralize(slot_name).to_sym)
|
84
82
|
|
85
83
|
setter_method_name = :"with_#{slot_name}"
|
86
84
|
|
87
|
-
define_method setter_method_name do |*args, &block|
|
88
|
-
|
85
|
+
define_method setter_method_name do |*args, **kwargs, &block|
|
86
|
+
__vc_set_slot(slot_name, nil, *args, **kwargs, &block)
|
89
87
|
end
|
90
|
-
ruby2_keywords(setter_method_name) if respond_to?(:ruby2_keywords, true)
|
91
88
|
|
92
89
|
self::GeneratedSlotMethods.define_method slot_name do
|
93
|
-
|
90
|
+
__vc_get_slot(slot_name)
|
94
91
|
end
|
95
92
|
|
96
93
|
self::GeneratedSlotMethods.define_method :"#{slot_name}?" do
|
97
|
-
|
94
|
+
__vc_get_slot(slot_name).present?
|
98
95
|
end
|
99
96
|
|
100
97
|
define_method :"with_#{slot_name}_content" do |content|
|
@@ -103,7 +100,7 @@ module ViewComponent
|
|
103
100
|
self
|
104
101
|
end
|
105
102
|
|
106
|
-
|
103
|
+
__vc_register_slot(slot_name, collection: false, callable: callable)
|
107
104
|
end
|
108
105
|
end
|
109
106
|
|
@@ -145,20 +142,19 @@ module ViewComponent
|
|
145
142
|
# <% end %>
|
146
143
|
# <% end %>
|
147
144
|
def renders_many(slot_name, callable = nil)
|
148
|
-
|
145
|
+
__vc_validate_plural_slot_name(slot_name)
|
149
146
|
|
150
147
|
if callable.is_a?(Hash) && callable.key?(:types)
|
151
|
-
|
148
|
+
__vc_register_polymorphic_slot(slot_name, callable[:types], collection: true)
|
152
149
|
else
|
153
150
|
singular_name = ActiveSupport::Inflector.singularize(slot_name)
|
154
|
-
|
151
|
+
__vc_validate_singular_slot_name(ActiveSupport::Inflector.singularize(slot_name).to_sym)
|
155
152
|
|
156
153
|
setter_method_name = :"with_#{singular_name}"
|
157
154
|
|
158
|
-
define_method setter_method_name do |*args, &block|
|
159
|
-
|
155
|
+
define_method setter_method_name do |*args, **kwargs, &block|
|
156
|
+
__vc_set_slot(slot_name, nil, *args, **kwargs, &block)
|
160
157
|
end
|
161
|
-
ruby2_keywords(setter_method_name) if respond_to?(:ruby2_keywords, true)
|
162
158
|
|
163
159
|
define_method :"with_#{singular_name}_content" do |content|
|
164
160
|
send(setter_method_name) { content.to_s }
|
@@ -169,22 +165,22 @@ module ViewComponent
|
|
169
165
|
define_method :"with_#{slot_name}" do |collection_args = nil, &block|
|
170
166
|
collection_args.map do |args|
|
171
167
|
if args.respond_to?(:to_hash)
|
172
|
-
|
168
|
+
__vc_set_slot(slot_name, nil, **args, &block)
|
173
169
|
else
|
174
|
-
|
170
|
+
__vc_set_slot(slot_name, nil, *args, &block)
|
175
171
|
end
|
176
172
|
end
|
177
173
|
end
|
178
174
|
|
179
175
|
self::GeneratedSlotMethods.define_method slot_name do
|
180
|
-
|
176
|
+
__vc_get_slot(slot_name)
|
181
177
|
end
|
182
178
|
|
183
179
|
self::GeneratedSlotMethods.define_method :"#{slot_name}?" do
|
184
|
-
|
180
|
+
__vc_get_slot(slot_name).present?
|
185
181
|
end
|
186
182
|
|
187
|
-
|
183
|
+
__vc_register_slot(slot_name, collection: true, callable: callable)
|
188
184
|
end
|
189
185
|
end
|
190
186
|
|
@@ -215,13 +211,30 @@ module ViewComponent
|
|
215
211
|
super
|
216
212
|
end
|
217
213
|
|
218
|
-
|
214
|
+
# @private
|
215
|
+
# Called by the compiler, as instance methods are not defined when slots are first registered
|
216
|
+
def __vc_register_default_slots
|
217
|
+
registered_slots.each do |slot_name, config|
|
218
|
+
default_method_name = :"default_#{slot_name}"
|
219
|
+
config[:default_method] = instance_methods.find { |method_name| method_name == default_method_name }
|
220
|
+
|
221
|
+
registered_slots[slot_name] = config
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
private
|
226
|
+
|
227
|
+
def __vc_register_slot(slot_name, **kwargs)
|
228
|
+
registered_slots[slot_name] = __vc_define_slot(slot_name, **kwargs)
|
229
|
+
end
|
230
|
+
|
231
|
+
def __vc_register_polymorphic_slot(slot_name, types, collection:)
|
219
232
|
self::GeneratedSlotMethods.define_method(slot_name) do
|
220
|
-
|
233
|
+
__vc_get_slot(slot_name)
|
221
234
|
end
|
222
235
|
|
223
236
|
self::GeneratedSlotMethods.define_method(:"#{slot_name}?") do
|
224
|
-
|
237
|
+
__vc_get_slot(slot_name).present?
|
225
238
|
end
|
226
239
|
|
227
240
|
renderable_hash = types.each_with_object({}) do |(poly_type, poly_attributes_or_callable), memo|
|
@@ -240,7 +253,7 @@ module ViewComponent
|
|
240
253
|
"#{slot_name}_#{poly_type}"
|
241
254
|
end
|
242
255
|
|
243
|
-
memo[poly_type] =
|
256
|
+
memo[poly_type] = __vc_define_slot(
|
244
257
|
poly_slot_name, collection: collection, callable: poly_callable
|
245
258
|
)
|
246
259
|
|
@@ -250,10 +263,9 @@ module ViewComponent
|
|
250
263
|
raise AlreadyDefinedPolymorphicSlotSetterError.new(setter_method_name, poly_slot_name)
|
251
264
|
end
|
252
265
|
|
253
|
-
define_method(setter_method_name) do |*args, &block|
|
254
|
-
|
266
|
+
define_method(setter_method_name) do |*args, **kwargs, &block|
|
267
|
+
__vc_set_polymorphic_slot(slot_name, poly_type, *args, **kwargs, &block)
|
255
268
|
end
|
256
|
-
ruby2_keywords(setter_method_name) if respond_to?(:ruby2_keywords, true)
|
257
269
|
|
258
270
|
define_method :"with_#{poly_slot_name}_content" do |content|
|
259
271
|
send(setter_method_name) { content.to_s }
|
@@ -268,22 +280,7 @@ module ViewComponent
|
|
268
280
|
}
|
269
281
|
end
|
270
282
|
|
271
|
-
|
272
|
-
def register_default_slots
|
273
|
-
registered_slots.each do |slot_name, config|
|
274
|
-
config[:default_method] = instance_methods.find { |method_name| method_name == :"default_#{slot_name}" }
|
275
|
-
|
276
|
-
registered_slots[slot_name] = config
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
|
-
private
|
281
|
-
|
282
|
-
def register_slot(slot_name, **kwargs)
|
283
|
-
registered_slots[slot_name] = define_slot(slot_name, **kwargs)
|
284
|
-
end
|
285
|
-
|
286
|
-
def define_slot(slot_name, collection:, callable:)
|
283
|
+
def __vc_define_slot(slot_name, collection:, callable:)
|
287
284
|
slot = {collection: collection}
|
288
285
|
return slot unless callable
|
289
286
|
|
@@ -306,18 +303,18 @@ module ViewComponent
|
|
306
303
|
slot
|
307
304
|
end
|
308
305
|
|
309
|
-
def
|
306
|
+
def __vc_validate_plural_slot_name(slot_name)
|
310
307
|
if RESERVED_NAMES[:plural].include?(slot_name.to_sym)
|
311
308
|
raise ReservedPluralSlotNameError.new(name, slot_name)
|
312
309
|
end
|
313
310
|
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
311
|
+
__vc_raise_if_slot_name_uncountable(slot_name)
|
312
|
+
__vc_raise_if_slot_conflicts_with_call(slot_name)
|
313
|
+
__vc_raise_if_slot_ends_with_question_mark(slot_name)
|
314
|
+
__vc_raise_if_slot_registered(slot_name)
|
318
315
|
end
|
319
316
|
|
320
|
-
def
|
317
|
+
def __vc_validate_singular_slot_name(slot_name)
|
321
318
|
if slot_name.to_sym == :content
|
322
319
|
raise ContentSlotNameError.new(name)
|
323
320
|
end
|
@@ -326,28 +323,28 @@ module ViewComponent
|
|
326
323
|
raise ReservedSingularSlotNameError.new(name, slot_name)
|
327
324
|
end
|
328
325
|
|
329
|
-
|
330
|
-
|
331
|
-
|
326
|
+
__vc_raise_if_slot_conflicts_with_call(slot_name)
|
327
|
+
__vc_raise_if_slot_ends_with_question_mark(slot_name)
|
328
|
+
__vc_raise_if_slot_registered(slot_name)
|
332
329
|
end
|
333
330
|
|
334
|
-
def
|
331
|
+
def __vc_raise_if_slot_registered(slot_name)
|
335
332
|
if registered_slots.key?(slot_name)
|
336
333
|
raise RedefinedSlotError.new(name, slot_name)
|
337
334
|
end
|
338
335
|
end
|
339
336
|
|
340
|
-
def
|
337
|
+
def __vc_raise_if_slot_ends_with_question_mark(slot_name)
|
341
338
|
raise SlotPredicateNameError.new(name, slot_name) if slot_name.to_s.end_with?("?")
|
342
339
|
end
|
343
340
|
|
344
|
-
def
|
341
|
+
def __vc_raise_if_slot_conflicts_with_call(slot_name)
|
345
342
|
if slot_name.start_with?("call_")
|
346
343
|
raise InvalidSlotNameError, "Slot cannot start with 'call_'. Please rename #{slot_name}"
|
347
344
|
end
|
348
345
|
end
|
349
346
|
|
350
|
-
def
|
347
|
+
def __vc_raise_if_slot_name_uncountable(slot_name)
|
351
348
|
slot_name = slot_name.to_s
|
352
349
|
if slot_name.pluralize == slot_name.singularize
|
353
350
|
raise UncountableSlotNameError.new(name, slot_name)
|
@@ -355,22 +352,32 @@ module ViewComponent
|
|
355
352
|
end
|
356
353
|
end
|
357
354
|
|
358
|
-
def
|
359
|
-
content unless content_evaluated? # ensure content is loaded so slots will be defined
|
360
|
-
|
361
|
-
slot = self.class.registered_slots[slot_name]
|
355
|
+
def __vc_get_slot(slot_name)
|
362
356
|
@__vc_set_slots ||= {}
|
357
|
+
content unless defined?(@__vc_content_evaluated) && @__vc_content_evaluated # ensure content is loaded so slots will be defined
|
363
358
|
|
364
|
-
|
365
|
-
|
366
|
-
end
|
359
|
+
# If the slot is set, return it
|
360
|
+
return @__vc_set_slots[slot_name] if @__vc_set_slots[slot_name]
|
367
361
|
|
368
|
-
|
362
|
+
# If there is a default method for the slot, call it
|
363
|
+
if (default_method = registered_slots[slot_name][:default_method])
|
364
|
+
renderable_value = send(default_method)
|
365
|
+
slot = Slot.new(self)
|
366
|
+
|
367
|
+
if renderable_value.respond_to?(:render_in)
|
368
|
+
slot.__vc_component_instance = renderable_value
|
369
|
+
else
|
370
|
+
slot.__vc_content = renderable_value
|
371
|
+
end
|
372
|
+
|
373
|
+
slot
|
374
|
+
elsif self.class.registered_slots[slot_name][:collection]
|
375
|
+
# If empty slot is a collection, return an empty array
|
369
376
|
[]
|
370
377
|
end
|
371
378
|
end
|
372
379
|
|
373
|
-
def
|
380
|
+
def __vc_set_slot(slot_name, slot_definition = nil, *args, **kwargs, &block)
|
374
381
|
slot_definition ||= self.class.registered_slots[slot_name]
|
375
382
|
slot = Slot.new(self)
|
376
383
|
|
@@ -387,11 +394,11 @@ module ViewComponent
|
|
387
394
|
|
388
395
|
# If class
|
389
396
|
if slot_definition[:renderable]
|
390
|
-
slot.__vc_component_instance = slot_definition[:renderable].new(*args)
|
397
|
+
slot.__vc_component_instance = slot_definition[:renderable].new(*args, **kwargs)
|
391
398
|
# If class name as a string
|
392
399
|
elsif slot_definition[:renderable_class_name]
|
393
400
|
slot.__vc_component_instance =
|
394
|
-
self.class.const_get(slot_definition[:renderable_class_name]).new(*args)
|
401
|
+
self.class.const_get(slot_definition[:renderable_class_name]).new(*args, **kwargs)
|
395
402
|
# If passed a lambda
|
396
403
|
elsif slot_definition[:renderable_function]
|
397
404
|
# Use `bind(self)` to ensure lambda is executed in the context of the
|
@@ -400,11 +407,11 @@ module ViewComponent
|
|
400
407
|
renderable_function = slot_definition[:renderable_function].bind(self)
|
401
408
|
renderable_value =
|
402
409
|
if block
|
403
|
-
renderable_function.call(*args) do |*rargs|
|
410
|
+
renderable_function.call(*args, **kwargs) do |*rargs|
|
404
411
|
view_context.capture(*rargs, &block)
|
405
412
|
end
|
406
413
|
else
|
407
|
-
renderable_function.call(*args)
|
414
|
+
renderable_function.call(*args, **kwargs)
|
408
415
|
end
|
409
416
|
|
410
417
|
# Function calls can return components, so if it's a component handle it specially
|
@@ -426,9 +433,8 @@ module ViewComponent
|
|
426
433
|
|
427
434
|
slot
|
428
435
|
end
|
429
|
-
ruby2_keywords(:set_slot) if respond_to?(:ruby2_keywords, true)
|
430
436
|
|
431
|
-
def
|
437
|
+
def __vc_set_polymorphic_slot(slot_name, poly_type = nil, *args, **kwargs, &block)
|
432
438
|
slot_definition = self.class.registered_slots[slot_name]
|
433
439
|
|
434
440
|
if !slot_definition[:collection] && defined?(@__vc_set_slots) && @__vc_set_slots[slot_name]
|
@@ -437,8 +443,7 @@ module ViewComponent
|
|
437
443
|
|
438
444
|
poly_def = slot_definition[:renderable_hash][poly_type]
|
439
445
|
|
440
|
-
|
446
|
+
__vc_set_slot(slot_name, poly_def, *args, **kwargs, &block)
|
441
447
|
end
|
442
|
-
ruby2_keywords(:set_polymorphic_slot) if respond_to?(:ruby2_keywords, true)
|
443
448
|
end
|
444
449
|
end
|
@@ -4,7 +4,6 @@ module ViewComponent
|
|
4
4
|
module SystemTestHelpers
|
5
5
|
include TestHelpers
|
6
6
|
|
7
|
-
#
|
8
7
|
# Returns a block that can be used to visit the path of the inline rendered component.
|
9
8
|
# @param fragment [Nokogiri::Fragment] The fragment returned from `render_inline`.
|
10
9
|
# @param layout [String] The (optional) layout to use.
|
@@ -18,7 +17,7 @@ module ViewComponent
|
|
18
17
|
file.write(vc_test_controller.render_to_string(html: fragment.to_html.html_safe, layout: layout))
|
19
18
|
file.rewind
|
20
19
|
|
21
|
-
|
20
|
+
yield("/_system_test_entrypoint?file=#{file.path.split("/").last}")
|
22
21
|
ensure
|
23
22
|
file.unlink
|
24
23
|
end
|