view_component 3.23.2 → 4.0.0
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 +404 -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 +154 -157
- data/lib/view_component/collection.rb +11 -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 +30 -558
- 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
@@ -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
|
@@ -2,69 +2,115 @@
|
|
2
2
|
|
3
3
|
module ViewComponent
|
4
4
|
class Template
|
5
|
+
DEFAULT_FORMAT = :html
|
6
|
+
private_constant :DEFAULT_FORMAT
|
7
|
+
|
5
8
|
DataWithSource = Struct.new(:format, :identifier, :short_identifier, :type, keyword_init: true)
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
this_format: nil,
|
14
|
-
variant: nil,
|
15
|
-
lineno: nil,
|
16
|
-
path: nil,
|
17
|
-
extension: nil,
|
18
|
-
source: nil,
|
19
|
-
method_name: nil,
|
20
|
-
defined_on_self: true
|
21
|
-
)
|
9
|
+
|
10
|
+
attr_reader :details, :path
|
11
|
+
|
12
|
+
delegate :virtual_path, to: :@component
|
13
|
+
delegate :format, :variant, to: :@details
|
14
|
+
|
15
|
+
def initialize(component:, details:, lineno: nil, path: nil)
|
22
16
|
@component = component
|
23
|
-
@
|
24
|
-
@this_format = this_format
|
25
|
-
@variant = variant&.to_sym
|
17
|
+
@details = details
|
26
18
|
@lineno = lineno
|
27
19
|
@path = path
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
20
|
+
end
|
21
|
+
|
22
|
+
class File < Template
|
23
|
+
def initialize(component:, details:, path:)
|
24
|
+
super(
|
25
|
+
component: component,
|
26
|
+
details: details,
|
27
|
+
path: path,
|
28
|
+
lineno: 0
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def type
|
33
|
+
:file
|
34
|
+
end
|
35
|
+
|
36
|
+
# Load file each time we look up #source in case the file has been modified
|
37
|
+
def source
|
38
|
+
::File.read(@path)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class Inline < Template
|
43
|
+
attr_reader :source
|
44
|
+
|
45
|
+
def initialize(component:, inline_template:)
|
46
|
+
details = ActionView::TemplateDetails.new(nil, inline_template.language.to_sym, nil, nil)
|
47
|
+
|
48
|
+
super(
|
49
|
+
component: component,
|
50
|
+
details: details,
|
51
|
+
path: inline_template.path,
|
52
|
+
lineno: inline_template.lineno,
|
53
|
+
)
|
54
|
+
|
55
|
+
@source = inline_template.source.dup
|
56
|
+
end
|
57
|
+
|
58
|
+
def type
|
59
|
+
:inline
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class InlineCall < Template
|
64
|
+
def initialize(component:, method_name:, defined_on_self:)
|
65
|
+
variant = method_name.to_s.include?("call_") ? method_name.to_s.sub("call_", "").to_sym : nil
|
66
|
+
details = ActionView::TemplateDetails.new(nil, nil, nil, variant)
|
67
|
+
|
68
|
+
super(component: component, details: details)
|
69
|
+
|
70
|
+
@call_method_name = method_name
|
71
|
+
@defined_on_self = defined_on_self
|
72
|
+
end
|
73
|
+
|
74
|
+
def type
|
75
|
+
:inline_call
|
76
|
+
end
|
77
|
+
|
78
|
+
def compile_to_component
|
79
|
+
@component.define_method(safe_method_name, @component.instance_method(@call_method_name))
|
80
|
+
end
|
81
|
+
|
82
|
+
def safe_method_name_call
|
83
|
+
m = safe_method_name
|
84
|
+
proc do
|
85
|
+
__vc_maybe_escape_html(send(m)) do
|
86
|
+
Kernel.warn("WARNING: The #{self.class} component rendered HTML-unsafe output. " \
|
87
|
+
"The output will be automatically escaped, but you may want to investigate.")
|
88
|
+
end
|
43
89
|
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def defined_on_self?
|
93
|
+
@defined_on_self
|
94
|
+
end
|
44
95
|
end
|
45
96
|
|
46
97
|
def compile_to_component
|
47
|
-
|
48
|
-
@component.silence_redefinition_of_method(@call_method_name)
|
98
|
+
@component.silence_redefinition_of_method(call_method_name)
|
49
99
|
|
50
|
-
|
51
|
-
|
52
|
-
def #{
|
100
|
+
# rubocop:disable Style/EvalWithLocation
|
101
|
+
@component.class_eval <<~RUBY, @path, @lineno
|
102
|
+
def #{call_method_name}
|
53
103
|
#{compiled_source}
|
54
104
|
end
|
55
|
-
|
56
|
-
|
57
|
-
end
|
105
|
+
RUBY
|
106
|
+
# rubocop:enable Style/EvalWithLocation
|
58
107
|
|
59
108
|
@component.define_method(safe_method_name, @component.instance_method(@call_method_name))
|
60
109
|
end
|
61
110
|
|
62
111
|
def safe_method_name_call
|
63
|
-
|
64
|
-
|
65
|
-
"maybe_escape_html(#{safe_method_name}) " \
|
66
|
-
"{ Kernel.warn(\"WARNING: The #{@component} component rendered HTML-unsafe output. " \
|
67
|
-
"The output will be automatically escaped, but you may want to investigate.\") } "
|
112
|
+
m = safe_method_name
|
113
|
+
proc { send(m) }
|
68
114
|
end
|
69
115
|
|
70
116
|
def requires_compiled_superclass?
|
@@ -72,63 +118,40 @@ module ViewComponent
|
|
72
118
|
end
|
73
119
|
|
74
120
|
def inline_call?
|
75
|
-
|
76
|
-
end
|
77
|
-
|
78
|
-
def inline?
|
79
|
-
@type == :inline
|
121
|
+
type == :inline_call
|
80
122
|
end
|
81
123
|
|
82
124
|
def default_format?
|
83
|
-
|
125
|
+
format.nil? || format == DEFAULT_FORMAT
|
84
126
|
end
|
127
|
+
alias_method :html?, :default_format?
|
85
128
|
|
86
|
-
def
|
87
|
-
@
|
129
|
+
def call_method_name
|
130
|
+
@call_method_name ||=
|
131
|
+
["call", (normalized_variant_name if variant.present?), (format unless default_format?)]
|
132
|
+
.compact.join("_").to_sym
|
88
133
|
end
|
89
134
|
|
90
135
|
def safe_method_name
|
91
|
-
"_#{
|
136
|
+
"_#{call_method_name}_#{@component.name.underscore.gsub("/", "__")}"
|
92
137
|
end
|
93
138
|
|
94
139
|
def normalized_variant_name
|
95
|
-
|
96
|
-
end
|
97
|
-
|
98
|
-
def defined_on_self?
|
99
|
-
@defined_on_self
|
140
|
+
variant.to_s.gsub("-", "__")
|
100
141
|
end
|
101
142
|
|
102
143
|
private
|
103
144
|
|
104
|
-
def source
|
105
|
-
if @source_originally_nil
|
106
|
-
# Load file each time we look up #source in case the file has been modified
|
107
|
-
File.read(@path)
|
108
|
-
else
|
109
|
-
@source
|
110
|
-
end
|
111
|
-
end
|
112
|
-
|
113
145
|
def compiled_source
|
114
|
-
handler =
|
146
|
+
handler = details.handler_class
|
115
147
|
this_source = source
|
116
148
|
this_source.rstrip! if @component.strip_trailing_whitespace?
|
117
149
|
|
118
150
|
short_identifier = defined?(Rails.root) ? @path.sub("#{Rails.root}/", "") : @path
|
119
|
-
|
151
|
+
format = self.format || DEFAULT_FORMAT
|
152
|
+
type = ActionView::Template::Types[format]
|
120
153
|
|
121
|
-
|
122
|
-
handler.call(
|
123
|
-
DataWithSource.new(format: @this_format, identifier: @path, short_identifier: short_identifier, type: type),
|
124
|
-
this_source
|
125
|
-
)
|
126
|
-
# :nocov:
|
127
|
-
# TODO: Remove in v4
|
128
|
-
else
|
129
|
-
handler.call(DataNoSource.new(source: this_source, identifier: @path, type: type))
|
130
|
-
end
|
131
|
-
# :nocov:
|
154
|
+
handler.call(DataWithSource.new(format:, identifier: @path, short_identifier:, type:), this_source)
|
132
155
|
end
|
133
156
|
end
|
134
157
|
end
|