view_component 2.34.0 → 2.35.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.
Potentially problematic release.
This version of view_component might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/app/assets/vendor/prism.css +196 -0
- data/app/assets/vendor/prism.min.js +12 -0
- data/app/helpers/preview_helper.rb +19 -0
- data/app/views/view_components/_preview_source.html.erb +17 -0
- data/app/views/view_components/preview.html.erb +6 -2
- data/{CHANGELOG.md → docs/CHANGELOG.md} +74 -2
- data/lib/rails/generators/abstract_generator.rb +29 -0
- data/lib/rails/generators/component/component_generator.rb +5 -5
- data/lib/rails/generators/erb/component_generator.rb +7 -16
- data/lib/rails/generators/haml/component_generator.rb +6 -16
- data/lib/rails/generators/slim/component_generator.rb +6 -16
- data/lib/view_component.rb +1 -0
- data/lib/view_component/base.rb +94 -72
- data/lib/view_component/collection.rb +2 -1
- data/lib/view_component/compile_cache.rb +1 -0
- data/lib/view_component/compiler.rb +53 -49
- data/lib/view_component/content_areas.rb +50 -0
- data/lib/view_component/engine.rb +6 -0
- data/lib/view_component/preview.rb +14 -7
- data/lib/view_component/previewable.rb +16 -18
- data/lib/view_component/slot_v2.rb +28 -27
- data/lib/view_component/slotable.rb +2 -1
- data/lib/view_component/slotable_v2.rb +19 -18
- data/lib/view_component/translatable.rb +6 -5
- data/lib/view_component/version.rb +1 -1
- data/lib/view_component/with_content_helper.rb +1 -1
- data/lib/yard/mattr_accessor_handler.rb +19 -0
- metadata +74 -39
@@ -5,27 +5,17 @@ require "rails/generators/erb/component_generator"
|
|
5
5
|
module Haml
|
6
6
|
module Generators
|
7
7
|
class ComponentGenerator < Erb::Generators::ComponentGenerator
|
8
|
+
include ViewComponent::AbstractGenerator
|
9
|
+
|
8
10
|
source_root File.expand_path("templates", __dir__)
|
9
11
|
class_option :sidecar, type: :boolean, default: false
|
10
12
|
|
11
|
-
def
|
12
|
-
|
13
|
-
template "component.html.haml", destination
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
def destination
|
20
|
-
if options["sidecar"]
|
21
|
-
File.join("app/components", class_path, "#{file_name}_component", "#{file_name}_component.html.haml")
|
22
|
-
else
|
23
|
-
File.join("app/components", class_path, "#{file_name}_component.html.haml")
|
24
|
-
end
|
13
|
+
def engine_name
|
14
|
+
"haml"
|
25
15
|
end
|
26
16
|
|
27
|
-
def
|
28
|
-
|
17
|
+
def copy_view_file
|
18
|
+
super
|
29
19
|
end
|
30
20
|
end
|
31
21
|
end
|
@@ -5,27 +5,17 @@ require "rails/generators/erb/component_generator"
|
|
5
5
|
module Slim
|
6
6
|
module Generators
|
7
7
|
class ComponentGenerator < Erb::Generators::ComponentGenerator
|
8
|
+
include ViewComponent::AbstractGenerator
|
9
|
+
|
8
10
|
source_root File.expand_path("templates", __dir__)
|
9
11
|
class_option :sidecar, type: :boolean, default: false
|
10
12
|
|
11
|
-
def
|
12
|
-
|
13
|
-
template "component.html.slim", destination
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
def destination
|
20
|
-
if options["sidecar"]
|
21
|
-
File.join("app/components", class_path, "#{file_name}_component", "#{file_name}_component.html.slim")
|
22
|
-
else
|
23
|
-
File.join("app/components", class_path, "#{file_name}_component.html.slim")
|
24
|
-
end
|
13
|
+
def engine_name
|
14
|
+
"slim"
|
25
15
|
end
|
26
16
|
|
27
|
-
def
|
28
|
-
|
17
|
+
def copy_view_file
|
18
|
+
super
|
29
19
|
end
|
30
20
|
end
|
31
21
|
end
|
data/lib/view_component.rb
CHANGED
data/lib/view_component/base.rb
CHANGED
@@ -4,6 +4,7 @@ require "action_view"
|
|
4
4
|
require "active_support/configurable"
|
5
5
|
require "view_component/collection"
|
6
6
|
require "view_component/compile_cache"
|
7
|
+
require "view_component/content_areas"
|
7
8
|
require "view_component/previewable"
|
8
9
|
require "view_component/slotable"
|
9
10
|
require "view_component/slotable_v2"
|
@@ -12,6 +13,7 @@ require "view_component/with_content_helper"
|
|
12
13
|
module ViewComponent
|
13
14
|
class Base < ActionView::Base
|
14
15
|
include ActiveSupport::Configurable
|
16
|
+
include ViewComponent::ContentAreas
|
15
17
|
include ViewComponent::Previewable
|
16
18
|
include ViewComponent::SlotableV2
|
17
19
|
include ViewComponent::WithContentHelper
|
@@ -76,22 +78,24 @@ module ViewComponent
|
|
76
78
|
@virtual_path ||= virtual_path
|
77
79
|
|
78
80
|
# For template variants (+phone, +desktop, etc.)
|
79
|
-
@
|
81
|
+
@__vc_variant ||= @lookup_context.variants.first
|
80
82
|
|
81
83
|
# For caching, such as #cache_if
|
82
84
|
@current_template = nil unless defined?(@current_template)
|
83
85
|
old_current_template = @current_template
|
84
86
|
@current_template = self
|
85
87
|
|
86
|
-
|
88
|
+
if block && defined?(@__vc_content_set_by_with_content)
|
89
|
+
raise ArgumentError.new("Block provided after calling `with_content`. Use one or the other.")
|
90
|
+
end
|
87
91
|
|
88
|
-
@
|
89
|
-
@
|
92
|
+
@__vc_content_evaluated = false
|
93
|
+
@__vc_render_in_block = block
|
90
94
|
|
91
95
|
before_render
|
92
96
|
|
93
97
|
if render?
|
94
|
-
render_template_for(@
|
98
|
+
render_template_for(@__vc_variant).to_s + _output_postamble
|
95
99
|
else
|
96
100
|
""
|
97
101
|
end
|
@@ -115,7 +119,7 @@ module ViewComponent
|
|
115
119
|
|
116
120
|
# Called after rendering the component.
|
117
121
|
#
|
118
|
-
# @deprecated Use
|
122
|
+
# @deprecated Use `#before_render` instead. Will be removed in v3.0.0.
|
119
123
|
# @return [void]
|
120
124
|
def before_render_check
|
121
125
|
# noop
|
@@ -151,7 +155,8 @@ module ViewComponent
|
|
151
155
|
# @return [ActionController::Base]
|
152
156
|
def controller
|
153
157
|
raise ViewContextCalledBeforeRenderError, "`controller` can only be called at render time." if view_context.nil?
|
154
|
-
|
158
|
+
|
159
|
+
@__vc_controller ||= view_context.controller
|
155
160
|
end
|
156
161
|
|
157
162
|
# A proxy through which to access helpers. Use sparingly as doing so introduces coupling that inhibits encapsulation & reuse, often making testing difficult.
|
@@ -159,7 +164,8 @@ module ViewComponent
|
|
159
164
|
# @return [ActionView::Base]
|
160
165
|
def helpers
|
161
166
|
raise ViewContextCalledBeforeRenderError, "`helpers` can only be called at render time." if view_context.nil?
|
162
|
-
|
167
|
+
|
168
|
+
@__vc_helpers ||= controller.view_context
|
163
169
|
end
|
164
170
|
|
165
171
|
# Exposes .virtual_path as an instance method
|
@@ -180,33 +186,17 @@ module ViewComponent
|
|
180
186
|
# @private
|
181
187
|
def format
|
182
188
|
# Ruby 2.6 throws a warning without checking `defined?`, 2.7 does not
|
183
|
-
if defined?(@
|
184
|
-
@
|
189
|
+
if defined?(@__vc_variant)
|
190
|
+
@__vc_variant
|
185
191
|
end
|
186
192
|
end
|
187
193
|
|
188
|
-
# Assign the provided content to the content area accessor
|
189
|
-
#
|
190
|
-
# @private
|
191
|
-
def with(area, content = nil, &block)
|
192
|
-
unless content_areas.include?(area)
|
193
|
-
raise ArgumentError.new "Unknown content_area '#{area}' - expected one of '#{content_areas}'"
|
194
|
-
end
|
195
|
-
|
196
|
-
if block_given?
|
197
|
-
content = view_context.capture(&block)
|
198
|
-
end
|
199
|
-
|
200
|
-
instance_variable_set("@#{area}".to_sym, content)
|
201
|
-
nil
|
202
|
-
end
|
203
|
-
|
204
194
|
# Use the provided variant instead of the one determined by the current request.
|
205
195
|
#
|
206
196
|
# @param variant [Symbol] The variant to be used by the component.
|
207
197
|
# @return [self]
|
208
198
|
def with_variant(variant)
|
209
|
-
@
|
199
|
+
@__vc_variant = variant
|
210
200
|
|
211
201
|
self
|
212
202
|
end
|
@@ -223,31 +213,54 @@ module ViewComponent
|
|
223
213
|
attr_reader :view_context
|
224
214
|
|
225
215
|
def content
|
226
|
-
|
227
|
-
@
|
228
|
-
|
229
|
-
@
|
230
|
-
view_context
|
231
|
-
|
232
|
-
@
|
233
|
-
|
216
|
+
@__vc_content_evaluated = true
|
217
|
+
return @__vc_content if defined?(@__vc_content)
|
218
|
+
|
219
|
+
@__vc_content =
|
220
|
+
if @view_context && @__vc_render_in_block
|
221
|
+
view_context.capture(self, &@__vc_render_in_block)
|
222
|
+
elsif defined?(@__vc_content_set_by_with_content)
|
223
|
+
@__vc_content_set_by_with_content
|
224
|
+
end
|
234
225
|
end
|
235
226
|
|
236
227
|
def content_evaluated?
|
237
|
-
@
|
228
|
+
@__vc_content_evaluated
|
238
229
|
end
|
239
230
|
|
240
|
-
#
|
241
|
-
#
|
242
|
-
#
|
243
|
-
#
|
231
|
+
# Set the controller used for testing components:
|
232
|
+
#
|
233
|
+
# config.view_component.test_controller = "MyTestController"
|
234
|
+
#
|
235
|
+
# Defaults to ApplicationController. Can also be configured on a per-test
|
236
|
+
# basis using `with_controller_class`.
|
237
|
+
#
|
244
238
|
mattr_accessor :test_controller
|
245
239
|
@@test_controller = "ApplicationController"
|
246
240
|
|
247
|
-
#
|
241
|
+
# Set if render monkey patches should be included or not in Rails <6.1:
|
242
|
+
#
|
243
|
+
# config.view_component.render_monkey_patch_enabled = false
|
244
|
+
#
|
248
245
|
mattr_accessor :render_monkey_patch_enabled, instance_writer: false, default: true
|
249
246
|
|
247
|
+
# Enable or disable source code previews in component previews:
|
248
|
+
#
|
249
|
+
# config.view_component.show_previews_source = true
|
250
|
+
#
|
251
|
+
# Defaults to `false`.
|
252
|
+
#
|
253
|
+
mattr_accessor :show_previews_source, instance_writer: false, default: false
|
254
|
+
|
255
|
+
# Path for component files
|
256
|
+
#
|
257
|
+
# config.view_component.view_component_path = "app/my_components"
|
258
|
+
#
|
259
|
+
# Defaults to "app/components".
|
260
|
+
mattr_accessor :view_component_path, instance_writer: false, default: "app/components"
|
261
|
+
|
250
262
|
class << self
|
263
|
+
# @private
|
251
264
|
attr_accessor :source_location, :virtual_path
|
252
265
|
|
253
266
|
# EXPERIMENTAL: This API is experimental and may be removed at any time.
|
@@ -257,6 +270,7 @@ module ViewComponent
|
|
257
270
|
# strings starting without the "dot", example: `["erb", "haml"]`.
|
258
271
|
#
|
259
272
|
# For example, one might collect sidecar CSS files that need to be compiled.
|
273
|
+
# @private TODO: add documentation
|
260
274
|
def _sidecar_files(extensions)
|
261
275
|
return [] unless source_location
|
262
276
|
|
@@ -277,11 +291,12 @@ module ViewComponent
|
|
277
291
|
# end
|
278
292
|
#
|
279
293
|
# Without this, `MyOtherComponent` will not look for `my_component/my_other_component.html.erb`
|
280
|
-
nested_component_files =
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
294
|
+
nested_component_files =
|
295
|
+
if name.include?("::") && component_name != filename
|
296
|
+
Dir["#{directory}/#{filename}/#{component_name}.*{#{extensions}}"]
|
297
|
+
else
|
298
|
+
[]
|
299
|
+
end
|
285
300
|
|
286
301
|
# view files in the same directory as the component
|
287
302
|
sidecar_files = Dir["#{directory}/#{component_name}.*{#{extensions}}"]
|
@@ -291,16 +306,24 @@ module ViewComponent
|
|
291
306
|
(sidecar_files - [source_location] + sidecar_directory_files + nested_component_files).uniq
|
292
307
|
end
|
293
308
|
|
294
|
-
# Render a component collection
|
309
|
+
# Render a component for each element in a collection ([documentation](/guide/collections)):
|
310
|
+
#
|
311
|
+
# render(ProductsComponent.with_collection(@products, foo: :bar))
|
312
|
+
#
|
313
|
+
# @param collection [Enumerable] A list of items to pass the ViewComponent one at a time.
|
314
|
+
# @param args [Arguments] Arguments to pass to the ViewComponent every time.
|
295
315
|
def with_collection(collection, **args)
|
296
316
|
Collection.new(self, collection, **args)
|
297
317
|
end
|
298
318
|
|
299
319
|
# Provide identifier for ActionView template annotations
|
320
|
+
#
|
321
|
+
# @private
|
300
322
|
def short_identifier
|
301
323
|
@short_identifier ||= defined?(Rails.root) ? source_location.sub("#{Rails.root}/", "") : source_location
|
302
324
|
end
|
303
325
|
|
326
|
+
# @private
|
304
327
|
def inherited(child)
|
305
328
|
# Compile so child will inherit compiled `call_*` template methods that
|
306
329
|
# `compile` defines
|
@@ -318,11 +341,14 @@ module ViewComponent
|
|
318
341
|
child.source_location = caller_locations(1, 10).reject { |l| l.label == "inherited" }[0].absolute_path
|
319
342
|
|
320
343
|
# Removes the first part of the path and the extension.
|
321
|
-
child.virtual_path = child.source_location.gsub(
|
344
|
+
child.virtual_path = child.source_location.gsub(
|
345
|
+
%r{(.*#{Regexp.quote(ViewComponent::Base.view_component_path)})|(\.rb)}, ""
|
346
|
+
)
|
322
347
|
|
323
348
|
super
|
324
349
|
end
|
325
350
|
|
351
|
+
# @private
|
326
352
|
def compiled?
|
327
353
|
compiler.compiled?
|
328
354
|
end
|
@@ -331,50 +357,39 @@ module ViewComponent
|
|
331
357
|
#
|
332
358
|
# Do as much work as possible in this step, as doing so reduces the amount
|
333
359
|
# of work done each time a component is rendered.
|
360
|
+
# @private
|
334
361
|
def compile(raise_errors: false)
|
335
362
|
compiler.compile(raise_errors: raise_errors)
|
336
363
|
end
|
337
364
|
|
365
|
+
# @private
|
338
366
|
def compiler
|
339
|
-
@
|
367
|
+
@__vc_compiler ||= Compiler.new(self)
|
340
368
|
end
|
341
369
|
|
342
370
|
# we'll eventually want to update this to support other types
|
371
|
+
# @private
|
343
372
|
def type
|
344
373
|
"text/html"
|
345
374
|
end
|
346
375
|
|
376
|
+
# @private
|
347
377
|
def format
|
348
378
|
:html
|
349
379
|
end
|
350
380
|
|
381
|
+
# @private
|
351
382
|
def identifier
|
352
383
|
source_location
|
353
384
|
end
|
354
385
|
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
raise ArgumentError.new ":content is a reserved content area name. Please use another name, such as ':body'"
|
363
|
-
end
|
364
|
-
|
365
|
-
areas.each do |area|
|
366
|
-
define_method area.to_sym do
|
367
|
-
content unless content_evaluated? # ensure content is loaded so content_areas will be defined
|
368
|
-
instance_variable_get(:"@#{area}") if instance_variable_defined?(:"@#{area}")
|
369
|
-
end
|
370
|
-
end
|
371
|
-
|
372
|
-
self.content_areas = areas
|
373
|
-
end
|
374
|
-
|
375
|
-
# Support overriding collection parameter name
|
376
|
-
def with_collection_parameter(param)
|
377
|
-
@provided_collection_parameter = param
|
386
|
+
# Set the parameter name used when rendering elements of a collection ([documentation](/guide/collections)):
|
387
|
+
#
|
388
|
+
# with_collection_parameter :item
|
389
|
+
#
|
390
|
+
# @param parameter [Symbol] The parameter name used when rendering elements of a collection.
|
391
|
+
def with_collection_parameter(parameter)
|
392
|
+
@provided_collection_parameter = parameter
|
378
393
|
end
|
379
394
|
|
380
395
|
# Ensure the component initializer accepts the
|
@@ -382,6 +397,7 @@ module ViewComponent
|
|
382
397
|
# validate that the default parameter name
|
383
398
|
# is accepted, as support for collection
|
384
399
|
# rendering is optional.
|
400
|
+
# @private TODO: add documentation
|
385
401
|
def validate_collection_parameter!(validate_default: false)
|
386
402
|
parameter = validate_default ? collection_parameter : provided_collection_parameter
|
387
403
|
|
@@ -406,6 +422,7 @@ module ViewComponent
|
|
406
422
|
# Ensure the component initializer does not define
|
407
423
|
# invalid parameters that could override the framework's
|
408
424
|
# methods.
|
425
|
+
# @private TODO: add documentation
|
409
426
|
def validate_initialization_parameters!
|
410
427
|
return unless initialize_parameter_names.include?(RESERVED_PARAMETER)
|
411
428
|
|
@@ -416,6 +433,7 @@ module ViewComponent
|
|
416
433
|
)
|
417
434
|
end
|
418
435
|
|
436
|
+
# @private
|
419
437
|
def collection_parameter
|
420
438
|
if provided_collection_parameter
|
421
439
|
provided_collection_parameter
|
@@ -424,18 +442,22 @@ module ViewComponent
|
|
424
442
|
end
|
425
443
|
end
|
426
444
|
|
445
|
+
# @private
|
427
446
|
def collection_counter_parameter
|
428
447
|
"#{collection_parameter}_counter".to_sym
|
429
448
|
end
|
430
449
|
|
450
|
+
# @private
|
431
451
|
def counter_argument_present?
|
432
452
|
initialize_parameter_names.include?(collection_counter_parameter)
|
433
453
|
end
|
434
454
|
|
455
|
+
# @private
|
435
456
|
def collection_iteration_parameter
|
436
457
|
"#{collection_parameter}_iteration".to_sym
|
437
458
|
end
|
438
459
|
|
460
|
+
# @private
|
439
461
|
def iteration_argument_present?
|
440
462
|
initialize_parameter_names.include?(collection_iteration_parameter)
|
441
463
|
end
|
@@ -5,6 +5,7 @@ require "action_view/renderer/collection_renderer" if Rails.version.to_f >= 6.1
|
|
5
5
|
module ViewComponent
|
6
6
|
class Collection
|
7
7
|
attr_reader :component
|
8
|
+
|
8
9
|
delegate :format, to: :component
|
9
10
|
|
10
11
|
def render_in(view_context, &block)
|
@@ -16,7 +17,7 @@ module ViewComponent
|
|
16
17
|
content = component.new(**component_options(item, iterator)).render_in(view_context, &block)
|
17
18
|
iterator.iterate!
|
18
19
|
content
|
19
|
-
end.join.html_safe
|
20
|
+
end.join.html_safe # rubocop:disable Rails/OutputSafety
|
20
21
|
end
|
21
22
|
|
22
23
|
private
|