view_component 2.49.1 → 2.52.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/app/helpers/preview_helper.rb +2 -2
- data/docs/CHANGELOG.md +92 -7
- data/lib/rails/generators/abstract_generator.rb +1 -1
- data/lib/rails/generators/component/component_generator.rb +4 -2
- data/lib/rails/generators/locale/component_generator.rb +1 -1
- data/lib/rails/generators/preview/component_generator.rb +7 -0
- data/lib/rails/generators/preview/templates/component_preview.rb.tt +3 -1
- data/lib/rails/generators/rspec/templates/component_spec.rb.tt +2 -0
- data/lib/rails/generators/test_unit/templates/component_test.rb.tt +2 -0
- data/lib/view_component/base.rb +89 -38
- data/lib/view_component/compile_cache.rb +2 -1
- data/lib/view_component/compiler.rb +14 -12
- data/lib/view_component/content_areas.rb +1 -1
- data/lib/view_component/deprecation.rb +8 -0
- data/lib/view_component/docs_builder_component.html.erb +18 -0
- data/lib/view_component/docs_builder_component.rb +77 -0
- data/lib/view_component/engine.rb +20 -2
- data/lib/view_component/global_output_buffer.rb +99 -0
- data/lib/view_component/output_buffer_stack.rb +67 -0
- data/lib/view_component/polymorphic_slots.rb +8 -5
- data/lib/view_component/preview.rb +2 -2
- data/lib/view_component/slot_v2.rb +4 -10
- data/lib/view_component/slotable.rb +1 -1
- data/lib/view_component/slotable_v2.rb +30 -4
- data/lib/view_component/translatable.rb +1 -1
- data/lib/view_component/version.rb +2 -2
- data/lib/view_component.rb +5 -2
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1eb8c14aae22e2977db7cde77cd64f9ff1d69f14c0f2c95b6b2ebf0fabf67f5a
|
4
|
+
data.tar.gz: fe2c729ae013bf1dd2effb17143acfef756e915ea773dcb7eef84fd329c7e002
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e72fac02dd38cd39cfa7fe45bc57617a8088d2016add0c769df9075393dc8fde66ea44c96d466019354585da3754b1db2099e9207a46418b011e6ef8765b816
|
7
|
+
data.tar.gz: 6ec82473f583cd484e9ee4042683d7d605131279ef855a199f1a89292967dd971c6ed8a43f8ad7fc9d519bae02fb4f2e5cf8d14f4876ffa1b524ad41ce25ea20
|
@@ -27,8 +27,8 @@ module PreviewHelper
|
|
27
27
|
end.flatten
|
28
28
|
|
29
29
|
# Search for templates the contain `html`.
|
30
|
-
matching_templates = all_template_paths.find_all do |
|
31
|
-
|
30
|
+
matching_templates = all_template_paths.find_all do |path|
|
31
|
+
path =~ /#{template_identifier}*.(html)/
|
32
32
|
end
|
33
33
|
|
34
34
|
# In-case of a conflict due to multiple template files with
|
data/docs/CHANGELOG.md
CHANGED
@@ -7,9 +7,94 @@ title: Changelog
|
|
7
7
|
|
8
8
|
## main
|
9
9
|
|
10
|
+
## 2.52.0
|
11
|
+
|
12
|
+
* Add ADR for separate slot getter/setter API.
|
13
|
+
|
14
|
+
*Blake Williams*
|
15
|
+
|
16
|
+
* Add the option to use a "global" output buffer so `form_for` and friends can be used with view components.
|
17
|
+
|
18
|
+
*Cameron Dutro*, *Blake Williams*
|
19
|
+
|
20
|
+
* Fix fragment caching in partials when global output buffer is enabled.
|
21
|
+
* Fix template inheritance when eager loading is disabled.
|
22
|
+
|
23
|
+
*Cameron Dutro*
|
24
|
+
|
25
|
+
## 2.51.0
|
26
|
+
|
27
|
+
* Update the docs only when releasing a new version.
|
28
|
+
|
29
|
+
*Hans Lemuet*
|
30
|
+
|
31
|
+
* Alphabetize companies using ViewComponent and add Brightline to the list.
|
32
|
+
|
33
|
+
*Jack Schuss*
|
34
|
+
|
35
|
+
* Add CMYK value for ViewComponent Red color on logo page.
|
36
|
+
|
37
|
+
*Dylan Smith*
|
38
|
+
|
39
|
+
* Improve performance by moving template compilation from `#render_in` to `#render_template_for`.
|
40
|
+
|
41
|
+
*Cameron Dutro*
|
42
|
+
|
43
|
+
## 2.50.0
|
44
|
+
|
45
|
+
* Add tests for `layout` usage when rendering via controller.
|
46
|
+
|
47
|
+
*Felipe Sateler*
|
48
|
+
|
49
|
+
* Support returning Arrays from i18n files, and support marking them as HTML-safe translations.
|
50
|
+
|
51
|
+
*foca*
|
52
|
+
|
53
|
+
* Add Cometeer and Framework to users list.
|
54
|
+
|
55
|
+
*Elia Schito*
|
56
|
+
|
57
|
+
* Update Microsoft Vale styles.
|
58
|
+
|
59
|
+
*Simon Fish*
|
60
|
+
|
61
|
+
* Fix example in testing guide for how to setup default Rails tests.
|
62
|
+
|
63
|
+
*Steven Hansen*
|
64
|
+
|
65
|
+
* Update benchmark script to render multiple components/partials instead of a single instance per-run.
|
66
|
+
|
67
|
+
*Blake Williams*
|
68
|
+
|
69
|
+
* Add predicate methods `#{slot_name}?` to slots.
|
70
|
+
|
71
|
+
*Hans Lemuet*
|
72
|
+
|
73
|
+
* Use a dedicated deprecation instance, silence it while testing.
|
74
|
+
|
75
|
+
*Max Beizer, Hans Lemuet, Elia Schito*
|
76
|
+
|
77
|
+
* Fix Ruby warnings.
|
78
|
+
|
79
|
+
*Hans Lemuet*
|
80
|
+
|
81
|
+
* Place all generator options under `config.generate` namespace.
|
82
|
+
|
83
|
+
*Simon Fish*
|
84
|
+
|
85
|
+
* Allow preview generator to use provided component attributes.
|
86
|
+
* Add config option `config.view_component.generate.preview` to enable project-wide preview generation.
|
87
|
+
* Ensure all generated `.rb` files include `# frozen_string_literal: true` statement.
|
88
|
+
|
89
|
+
*Bob Maerten*
|
90
|
+
|
91
|
+
* Add Shogun to users list.
|
92
|
+
|
93
|
+
*Bernie Chiu*
|
94
|
+
|
10
95
|
## 2.49.1
|
11
96
|
|
12
|
-
* Patch XSS vulnerability in `Translatable` module caused by improperly escaped interpolation arguments.
|
97
|
+
* Patch XSS vulnerability in `ViewComponent::Translatable` module caused by improperly escaped interpolation arguments.
|
13
98
|
|
14
99
|
*Cameron Dutro*
|
15
100
|
|
@@ -619,6 +704,12 @@ title: Changelog
|
|
619
704
|
|
620
705
|
*Joel Hawksley*
|
621
706
|
|
707
|
+
## 2.31.2
|
708
|
+
|
709
|
+
* Patch XSS vulnerability in `ViewComponent::Translatable` module caused by improperly escaped interpolation arguments.
|
710
|
+
|
711
|
+
*Cameron Dutro*
|
712
|
+
|
622
713
|
## 2.31.1
|
623
714
|
|
624
715
|
* Fix `DEPRECATION WARNING: before_render_check` when compiling `ViewComponent::Base`
|
@@ -663,12 +754,6 @@ _Note: This release includes an underlying change to Slots that may affect incor
|
|
663
754
|
|
664
755
|
*Joel Hawksley*
|
665
756
|
|
666
|
-
## 2.29.1
|
667
|
-
|
668
|
-
* Patch XSS vulnerability in `ViewComponent::Translatable` module caused by improperly escaped interpolation arguments.
|
669
|
-
|
670
|
-
*Cameron Dutro*
|
671
|
-
|
672
757
|
## 2.29.0
|
673
758
|
|
674
759
|
* Allow Slot lambdas to share data from the parent component and allow chaining on the returned component.
|
@@ -11,11 +11,13 @@ module Rails
|
|
11
11
|
|
12
12
|
argument :attributes, type: :array, default: [], banner: "attribute"
|
13
13
|
check_class_collision suffix: "Component"
|
14
|
+
|
14
15
|
class_option :inline, type: :boolean, default: false
|
16
|
+
class_option :locale, type: :boolean, default: ViewComponent::Base.generate.locale
|
15
17
|
class_option :parent, type: :string, desc: "The parent class for the generated component"
|
16
|
-
class_option :
|
18
|
+
class_option :preview, type: :boolean, default: ViewComponent::Base.generate.preview
|
17
19
|
class_option :sidecar, type: :boolean, default: false
|
18
|
-
class_option :
|
20
|
+
class_option :stimulus, type: :boolean, default: ViewComponent::Base.generate.stimulus_controller
|
19
21
|
|
20
22
|
def create_component_file
|
21
23
|
template "component.rb", File.join(component_path, class_path, "#{file_name}_component.rb")
|
@@ -12,7 +12,7 @@ module Locale
|
|
12
12
|
class_option :sidecar, type: :boolean, default: false
|
13
13
|
|
14
14
|
def create_locale_file
|
15
|
-
if ViewComponent::Base.
|
15
|
+
if ViewComponent::Base.generate.distinct_locale_files
|
16
16
|
I18n.available_locales.each do |locale|
|
17
17
|
create_file destination(locale), translations_hash([locale]).to_yaml
|
18
18
|
end
|
@@ -5,6 +5,7 @@ module Preview
|
|
5
5
|
class ComponentGenerator < ::Rails::Generators::NamedBase
|
6
6
|
source_root File.expand_path("templates", __dir__)
|
7
7
|
|
8
|
+
argument :attributes, type: :array, default: [], banner: "attribute"
|
8
9
|
check_class_collision suffix: "ComponentPreview"
|
9
10
|
|
10
11
|
def create_preview_file
|
@@ -20,6 +21,12 @@ module Preview
|
|
20
21
|
def file_name
|
21
22
|
@_file_name ||= super.sub(/_component\z/i, "")
|
22
23
|
end
|
24
|
+
|
25
|
+
def render_signature
|
26
|
+
return if attributes.blank?
|
27
|
+
|
28
|
+
attributes.map { |attr| %(#{attr.name}: "#{attr.name}") }.join(", ")
|
29
|
+
end
|
23
30
|
end
|
24
31
|
end
|
25
32
|
end
|
data/lib/view_component/base.rb
CHANGED
@@ -40,6 +40,23 @@ module ViewComponent
|
|
40
40
|
# noop
|
41
41
|
end
|
42
42
|
|
43
|
+
# @!macro [attach] deprecated_generate_mattr_accessor
|
44
|
+
# @method generate_$1
|
45
|
+
# @deprecated Use `#generate.$1` instead. Will be removed in v3.0.0.
|
46
|
+
def self._deprecated_generate_mattr_accessor(name)
|
47
|
+
define_singleton_method("generate_#{name}".to_sym) do
|
48
|
+
generate.public_send(name)
|
49
|
+
end
|
50
|
+
define_singleton_method("generate_#{name}=".to_sym) do |value|
|
51
|
+
generate.public_send("#{name}=".to_sym, value)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
_deprecated_generate_mattr_accessor :distinct_locale_files
|
56
|
+
_deprecated_generate_mattr_accessor :locale
|
57
|
+
_deprecated_generate_mattr_accessor :sidecar
|
58
|
+
_deprecated_generate_mattr_accessor :stimulus_controller
|
59
|
+
|
43
60
|
# Entrypoint for rendering components.
|
44
61
|
#
|
45
62
|
# - `view_context`: ActionView context from calling view
|
@@ -49,11 +66,11 @@ module ViewComponent
|
|
49
66
|
#
|
50
67
|
# @return [String]
|
51
68
|
def render_in(view_context, &block)
|
52
|
-
self.class.compile(raise_errors: true)
|
53
|
-
|
54
69
|
@view_context = view_context
|
55
70
|
self.__vc_original_view_context ||= view_context
|
56
71
|
|
72
|
+
@output_buffer = ActionView::OutputBuffer.new unless @global_buffer_in_use
|
73
|
+
|
57
74
|
@lookup_context ||= view_context.lookup_context
|
58
75
|
|
59
76
|
# required for path helpers in older Rails versions
|
@@ -87,7 +104,7 @@ module ViewComponent
|
|
87
104
|
before_render
|
88
105
|
|
89
106
|
if render?
|
90
|
-
|
107
|
+
perform_render
|
91
108
|
else
|
92
109
|
""
|
93
110
|
end
|
@@ -95,6 +112,20 @@ module ViewComponent
|
|
95
112
|
@current_template = old_current_template
|
96
113
|
end
|
97
114
|
|
115
|
+
def perform_render
|
116
|
+
render_template_for(@__vc_variant).to_s + _output_postamble
|
117
|
+
end
|
118
|
+
|
119
|
+
# :nocov:
|
120
|
+
def render_template_for(variant = nil)
|
121
|
+
# Force compilation here so the compiler always redefines render_template_for.
|
122
|
+
# This is mostly a safeguard to prevent infinite recursion.
|
123
|
+
self.class.compile(raise_errors: true, force: true)
|
124
|
+
# .compile replaces this method; call the new one
|
125
|
+
render_template_for(variant)
|
126
|
+
end
|
127
|
+
# :nocov:
|
128
|
+
|
98
129
|
# EXPERIMENTAL: Optional content to be returned after the rendered template.
|
99
130
|
#
|
100
131
|
# @return [String]
|
@@ -218,14 +249,11 @@ module ViewComponent
|
|
218
249
|
# @param variant [Symbol] The variant to be used by the component.
|
219
250
|
# @return [self]
|
220
251
|
def with_variant(variant)
|
221
|
-
ActiveSupport::Deprecation.warn(
|
222
|
-
"`with_variant` is deprecated and will be removed in ViewComponent v3.0.0."
|
223
|
-
)
|
224
|
-
|
225
252
|
@__vc_variant = variant
|
226
253
|
|
227
254
|
self
|
228
255
|
end
|
256
|
+
deprecate :with_variant, deprecator: ViewComponent::Deprecation
|
229
257
|
|
230
258
|
# The current request. Use sparingly as doing so introduces coupling that
|
231
259
|
# inhibits encapsulation & reuse, often making testing difficult.
|
@@ -271,56 +299,63 @@ module ViewComponent
|
|
271
299
|
#
|
272
300
|
mattr_accessor :render_monkey_patch_enabled, instance_writer: false, default: true
|
273
301
|
|
274
|
-
#
|
302
|
+
# Path for component files
|
275
303
|
#
|
276
|
-
# config.view_component.
|
304
|
+
# config.view_component.view_component_path = "app/my_components"
|
277
305
|
#
|
278
|
-
# Defaults to `
|
306
|
+
# Defaults to `app/components`.
|
279
307
|
#
|
280
|
-
mattr_accessor :
|
308
|
+
mattr_accessor :view_component_path, instance_writer: false, default: "app/components"
|
281
309
|
|
282
|
-
#
|
310
|
+
# Parent class for generated components
|
283
311
|
#
|
284
|
-
# config.view_component.
|
312
|
+
# config.view_component.component_parent_class = "MyBaseComponent"
|
285
313
|
#
|
286
|
-
# Defaults to
|
314
|
+
# Defaults to nil. If this is falsy, generators will use
|
315
|
+
# "ApplicationComponent" if defined, "ViewComponent::Base" otherwise.
|
287
316
|
#
|
288
|
-
mattr_accessor :
|
317
|
+
mattr_accessor :component_parent_class, instance_writer: false
|
289
318
|
|
290
|
-
#
|
319
|
+
# Configuration for generators.
|
291
320
|
#
|
292
|
-
#
|
321
|
+
# All options under this namespace default to `false` unless otherwise
|
322
|
+
# stated.
|
293
323
|
#
|
294
|
-
#
|
324
|
+
# #### #sidecar
|
295
325
|
#
|
296
|
-
#
|
297
|
-
# Fallback on `[:en]` when no available_locales is defined.
|
326
|
+
# Always generate a component with a sidecar directory:
|
298
327
|
#
|
299
|
-
|
300
|
-
|
301
|
-
# Path for component files
|
328
|
+
# config.view_component.generate.sidecar = true
|
302
329
|
#
|
303
|
-
#
|
330
|
+
# #### #stimulus_controller
|
304
331
|
#
|
305
|
-
#
|
332
|
+
# Always generate a Stimulus controller alongside the component:
|
306
333
|
#
|
307
|
-
|
308
|
-
|
309
|
-
# Parent class for generated components
|
334
|
+
# config.view_component.generate.stimulus_controller = true
|
310
335
|
#
|
311
|
-
#
|
336
|
+
# #### #locale
|
337
|
+
#
|
338
|
+
# Always generate translations file alongside the component:
|
312
339
|
#
|
313
|
-
#
|
340
|
+
# config.view_component.generate.locale = true
|
314
341
|
#
|
315
|
-
|
316
|
-
|
317
|
-
# Always generate
|
342
|
+
# #### #distinct_locale_files
|
343
|
+
#
|
344
|
+
# Always generate as many translations files as available locales:
|
318
345
|
#
|
319
|
-
# config.view_component.
|
346
|
+
# config.view_component.generate.distinct_locale_files = true
|
320
347
|
#
|
321
|
-
#
|
348
|
+
# One file will be generated for each configured `I18n.available_locales`,
|
349
|
+
# falling back to `[:en]` when no `available_locales` is defined.
|
322
350
|
#
|
323
|
-
|
351
|
+
# #### #preview
|
352
|
+
#
|
353
|
+
# Always generate preview alongside the component:
|
354
|
+
#
|
355
|
+
# config.view_component.generate.preview = true
|
356
|
+
#
|
357
|
+
# Defaults to `false`.
|
358
|
+
mattr_accessor :generate, instance_writer: false, default: ActiveSupport::OrderedOptions.new(false)
|
324
359
|
|
325
360
|
class << self
|
326
361
|
# @private
|
@@ -392,6 +427,22 @@ module ViewComponent
|
|
392
427
|
# `compile` defines
|
393
428
|
compile
|
394
429
|
|
430
|
+
# Give the child its own personal #render_template_for to protect against the case when
|
431
|
+
# eager loading is disabled and the parent component is rendered before the child. In
|
432
|
+
# such a scenario, the parent will override ViewComponent::Base#render_template_for,
|
433
|
+
# meaning it will not be called for any children and thus not compile their templates.
|
434
|
+
if !child.instance_methods(false).include?(:render_template_for) && !child.compiled?
|
435
|
+
child.class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
436
|
+
def render_template_for(variant = nil)
|
437
|
+
# Force compilation here so the compiler always redefines render_template_for.
|
438
|
+
# This is mostly a safeguard to prevent infinite recursion.
|
439
|
+
self.class.compile(raise_errors: true, force: true)
|
440
|
+
# .compile replaces this method; call the new one
|
441
|
+
render_template_for(variant)
|
442
|
+
end
|
443
|
+
RUBY
|
444
|
+
end
|
445
|
+
|
395
446
|
# If Rails application is loaded, add application url_helpers to the component context
|
396
447
|
# we need to check this to use this gem as a dependency
|
397
448
|
if defined?(Rails) && Rails.application
|
@@ -424,8 +475,8 @@ module ViewComponent
|
|
424
475
|
# Do as much work as possible in this step, as doing so reduces the amount
|
425
476
|
# of work done each time a component is rendered.
|
426
477
|
# @private
|
427
|
-
def compile(raise_errors: false)
|
428
|
-
compiler.compile(raise_errors: raise_errors)
|
478
|
+
def compile(raise_errors: false, force: false)
|
479
|
+
compiler.compile(raise_errors: raise_errors, force: force)
|
429
480
|
end
|
430
481
|
|
431
482
|
# @private
|
@@ -27,12 +27,11 @@ module ViewComponent
|
|
27
27
|
self.class.mode == DEVELOPMENT_MODE
|
28
28
|
end
|
29
29
|
|
30
|
-
def compile(raise_errors: false)
|
31
|
-
return if compiled?
|
30
|
+
def compile(raise_errors: false, force: false)
|
31
|
+
return if compiled? && !force
|
32
|
+
return if component_class == ViewComponent::Base
|
32
33
|
|
33
34
|
with_lock do
|
34
|
-
CompileCache.invalidate_class!(component_class)
|
35
|
-
|
36
35
|
subclass_instance_methods = component_class.instance_methods(false)
|
37
36
|
|
38
37
|
if subclass_instance_methods.include?(:with_content) && raise_errors
|
@@ -49,7 +48,7 @@ module ViewComponent
|
|
49
48
|
end
|
50
49
|
|
51
50
|
if subclass_instance_methods.include?(:before_render_check)
|
52
|
-
|
51
|
+
ViewComponent::Deprecation.warn(
|
53
52
|
"`#before_render_check` will be removed in v3.0.0.\n\n" \
|
54
53
|
"To fix this issue, use `#before_render` instead."
|
55
54
|
)
|
@@ -65,13 +64,12 @@ module ViewComponent
|
|
65
64
|
# as Ruby warns when redefining a method.
|
66
65
|
method_name = call_method_name(template[:variant])
|
67
66
|
|
68
|
-
if component_class.instance_methods.include?(method_name.to_sym)
|
69
|
-
component_class.send(:
|
67
|
+
if component_class.instance_methods(false).include?(method_name.to_sym)
|
68
|
+
component_class.send(:remove_method, method_name.to_sym)
|
70
69
|
end
|
71
70
|
|
72
|
-
component_class.class_eval <<-RUBY, template[:path],
|
71
|
+
component_class.class_eval <<-RUBY, template[:path], 0
|
73
72
|
def #{method_name}
|
74
|
-
@output_buffer = ActionView::OutputBuffer.new
|
75
73
|
#{compiled_template(template[:path])}
|
76
74
|
end
|
77
75
|
RUBY
|
@@ -93,14 +91,18 @@ module ViewComponent
|
|
93
91
|
end
|
94
92
|
end
|
95
93
|
|
94
|
+
def reset_render_template_for
|
95
|
+
if component_class.instance_methods(false).include?(:render_template_for)
|
96
|
+
component_class.send(:remove_method, :render_template_for)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
96
100
|
private
|
97
101
|
|
98
102
|
attr_reader :component_class
|
99
103
|
|
100
104
|
def define_render_template_for
|
101
|
-
|
102
|
-
component_class.send(:undef_method, :render_template_for)
|
103
|
-
end
|
105
|
+
reset_render_template_for
|
104
106
|
|
105
107
|
variant_elsifs = variants.compact.uniq.map do |variant|
|
106
108
|
"elsif variant.to_sym == :#{variant}\n #{call_method_name(variant)}"
|
@@ -31,7 +31,7 @@ module ViewComponent
|
|
31
31
|
|
32
32
|
class_methods do
|
33
33
|
def with_content_areas(*areas)
|
34
|
-
|
34
|
+
ViewComponent::Deprecation.warn(
|
35
35
|
"`with_content_areas` is deprecated and will be removed in ViewComponent v3.0.0.\n\n" \
|
36
36
|
"Use slots (https://viewcomponent.org/guide/slots.html) instead."
|
37
37
|
)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
title: API
|
4
|
+
nav_order: 3
|
5
|
+
---
|
6
|
+
|
7
|
+
<!-- Warning: AUTO-GENERATED file, don't edit. Add code comments to your Ruby instead <3 -->
|
8
|
+
|
9
|
+
# API
|
10
|
+
|
11
|
+
<% @sections.each do |section| %>
|
12
|
+
## <%= section.heading %>
|
13
|
+
|
14
|
+
<% section.methods.each do |method| %>
|
15
|
+
### <%== render ViewComponent::DocsBuilderComponent::MethodDoc.new(method) %>
|
16
|
+
|
17
|
+
<% end %>
|
18
|
+
<% end %>
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
class DocsBuilderComponent < Base
|
5
|
+
class Section < Struct.new(:heading, :methods, :show_types, keyword_init: true)
|
6
|
+
def initialize(heading: nil, methods: [], show_types: true)
|
7
|
+
methods.sort_by! { |method| method[:name] }
|
8
|
+
super
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class MethodDoc < ViewComponent::Base
|
13
|
+
def initialize(method, section: Section.new(show_types: true))
|
14
|
+
@method = method
|
15
|
+
@section = section
|
16
|
+
end
|
17
|
+
|
18
|
+
def show_types?
|
19
|
+
@section.show_types
|
20
|
+
end
|
21
|
+
|
22
|
+
def deprecated?
|
23
|
+
@method.tag(:deprecated).present?
|
24
|
+
end
|
25
|
+
|
26
|
+
def suffix
|
27
|
+
" (Deprecated)" if deprecated?
|
28
|
+
end
|
29
|
+
|
30
|
+
def types
|
31
|
+
" → [#{@method.tag(:return).types.join(',')}]" if @method.tag(:return)&.types && show_types?
|
32
|
+
end
|
33
|
+
|
34
|
+
def signature_or_name
|
35
|
+
@method.signature ? @method.signature.gsub("def ", "") : @method.name
|
36
|
+
end
|
37
|
+
|
38
|
+
def separator
|
39
|
+
@method.sep
|
40
|
+
end
|
41
|
+
|
42
|
+
def docstring
|
43
|
+
@method.docstring
|
44
|
+
end
|
45
|
+
|
46
|
+
def deprecation_text
|
47
|
+
@method.tag(:deprecated)&.text
|
48
|
+
end
|
49
|
+
|
50
|
+
def docstring_and_deprecation_text
|
51
|
+
<<~DOCS.strip
|
52
|
+
#{docstring}
|
53
|
+
|
54
|
+
#{"_#{deprecation_text}_" if deprecated?}
|
55
|
+
DOCS
|
56
|
+
end
|
57
|
+
|
58
|
+
def call
|
59
|
+
<<~DOCS.chomp
|
60
|
+
#{separator}#{signature_or_name}#{types}#{suffix}
|
61
|
+
|
62
|
+
#{docstring_and_deprecation_text}
|
63
|
+
DOCS
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# { heading: String, public_only: Boolean, show_types: Boolean}
|
68
|
+
def initialize(sections: [])
|
69
|
+
@sections = sections
|
70
|
+
end
|
71
|
+
|
72
|
+
# deprecation
|
73
|
+
# return
|
74
|
+
# only public methods
|
75
|
+
# sig with types or name
|
76
|
+
end
|
77
|
+
end
|
@@ -20,6 +20,7 @@ module ViewComponent
|
|
20
20
|
options.instrumentation_enabled = false if options.instrumentation_enabled.nil?
|
21
21
|
options.preview_route ||= ViewComponent::Base.preview_route
|
22
22
|
options.preview_controller ||= ViewComponent::Base.preview_controller
|
23
|
+
options.use_global_output_buffer = false if options.use_global_output_buffer.nil?
|
23
24
|
|
24
25
|
if options.show_previews
|
25
26
|
options.preview_paths << "#{Rails.root}/test/components/previews" if defined?(Rails.root) && Dir.exist?(
|
@@ -27,7 +28,7 @@ module ViewComponent
|
|
27
28
|
)
|
28
29
|
|
29
30
|
if options.preview_path.present?
|
30
|
-
|
31
|
+
ViewComponent::Deprecation.warn(
|
31
32
|
"`preview_path` will be removed in v3.0.0. Use `preview_paths` instead."
|
32
33
|
)
|
33
34
|
options.preview_paths << options.preview_path
|
@@ -57,6 +58,21 @@ module ViewComponent
|
|
57
58
|
end
|
58
59
|
end
|
59
60
|
|
61
|
+
initializer "view_component.enable_global_output_buffer" do |app|
|
62
|
+
ActiveSupport.on_load(:view_component) do
|
63
|
+
env_use_gob = ENV.fetch("VIEW_COMPONENT_USE_GLOBAL_OUTPUT_BUFFER", "false") == "true"
|
64
|
+
config_use_gob = app.config.view_component.use_global_output_buffer
|
65
|
+
|
66
|
+
if config_use_gob || env_use_gob
|
67
|
+
# :nocov:
|
68
|
+
app.config.view_component.use_global_output_buffer = true
|
69
|
+
ViewComponent::Base.prepend(ViewComponent::GlobalOutputBuffer)
|
70
|
+
ActionView::Base.prepend(ViewComponent::GlobalOutputBuffer::ActionViewMods)
|
71
|
+
# :nocov:
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
60
76
|
initializer "view_component.set_autoload_paths" do |app|
|
61
77
|
options = app.config.view_component
|
62
78
|
|
@@ -155,7 +171,9 @@ end
|
|
155
171
|
|
156
172
|
# :nocov:
|
157
173
|
unless defined?(ViewComponent::Base)
|
158
|
-
|
174
|
+
require "view_component/deprecation"
|
175
|
+
|
176
|
+
ViewComponent::Deprecation.warn(
|
159
177
|
"This manually engine loading is deprecated and will be removed in v3.0.0. " \
|
160
178
|
"Remove `require \"view_component/engine\"`."
|
161
179
|
)
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
module GlobalOutputBuffer
|
5
|
+
def render_in(view_context, &block)
|
6
|
+
unless view_context.output_buffer.is_a?(OutputBufferStack)
|
7
|
+
# use instance_variable_set here to avoid triggering the code in the #output_buffer= method below
|
8
|
+
view_context.instance_variable_set(:@output_buffer, OutputBufferStack.new(view_context.output_buffer))
|
9
|
+
end
|
10
|
+
|
11
|
+
@output_buffer = view_context.output_buffer
|
12
|
+
@global_buffer_in_use = true
|
13
|
+
|
14
|
+
super(view_context, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def perform_render
|
18
|
+
# HAML unhelpfully assigns to @output_buffer directly, so we hold onto a reference to
|
19
|
+
# it and restore @output_buffer when the HAML engine is finished. In non-HAML cases,
|
20
|
+
# @output_buffer and orig_buf will point to the same object, making the reassignment
|
21
|
+
# statements no-ops.
|
22
|
+
orig_buf = @output_buffer
|
23
|
+
@output_buffer.push
|
24
|
+
result = render_template_for(@__vc_variant).to_s + _output_postamble
|
25
|
+
@output_buffer = orig_buf
|
26
|
+
@output_buffer.pop
|
27
|
+
result
|
28
|
+
end
|
29
|
+
|
30
|
+
def output_buffer=(other_buffer)
|
31
|
+
@output_buffer.replace(other_buffer)
|
32
|
+
end
|
33
|
+
|
34
|
+
def with_output_buffer(buf = nil)
|
35
|
+
unless buf
|
36
|
+
buf = ActionView::OutputBuffer.new
|
37
|
+
if output_buffer && output_buffer.respond_to?(:encoding)
|
38
|
+
buf.force_encoding(output_buffer.encoding)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
output_buffer.push(buf)
|
43
|
+
result = nil
|
44
|
+
|
45
|
+
begin
|
46
|
+
yield
|
47
|
+
ensure
|
48
|
+
# assign result here to avoid a return statement, which will
|
49
|
+
# immediately return to the caller and swallow any errors
|
50
|
+
result = output_buffer.pop
|
51
|
+
end
|
52
|
+
|
53
|
+
result
|
54
|
+
end
|
55
|
+
|
56
|
+
module ActionViewMods
|
57
|
+
def output_buffer=(other_buffer)
|
58
|
+
if @output_buffer.is_a?(OutputBufferStack)
|
59
|
+
@output_buffer.replace(other_buffer)
|
60
|
+
else
|
61
|
+
super
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def with_output_buffer(buf = nil)
|
66
|
+
unless buf
|
67
|
+
buf = ActionView::OutputBuffer.new
|
68
|
+
if @output_buffer && @output_buffer.respond_to?(:encoding)
|
69
|
+
buf.force_encoding(@output_buffer.encoding)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
result = nil
|
74
|
+
|
75
|
+
if @output_buffer.is_a?(OutputBufferStack)
|
76
|
+
@output_buffer.push(buf)
|
77
|
+
|
78
|
+
begin
|
79
|
+
yield
|
80
|
+
ensure
|
81
|
+
result = @output_buffer.pop
|
82
|
+
end
|
83
|
+
|
84
|
+
result
|
85
|
+
else
|
86
|
+
@output_buffer, old_buffer = buf, output_buffer
|
87
|
+
|
88
|
+
begin
|
89
|
+
yield
|
90
|
+
ensure
|
91
|
+
@output_buffer = old_buffer
|
92
|
+
end
|
93
|
+
|
94
|
+
buf
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
class OutputBufferStack
|
5
|
+
delegate_missing_to :@current_buffer
|
6
|
+
delegate :presence, :present?, :html_safe?, to: :@current_buffer
|
7
|
+
|
8
|
+
attr_reader :buffer_stack
|
9
|
+
|
10
|
+
def self.make_frame(*args)
|
11
|
+
ActionView::OutputBuffer.new(*args)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(initial_buffer = nil)
|
15
|
+
if initial_buffer.is_a?(self.class)
|
16
|
+
@current_buffer = self.class.make_frame(initial_buffer.current)
|
17
|
+
@buffer_stack = [*initial_buffer.buffer_stack[0..-2], @current_buffer]
|
18
|
+
else
|
19
|
+
@current_buffer = initial_buffer || self.class.make_frame
|
20
|
+
@buffer_stack = [@current_buffer]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def replace(buffer)
|
25
|
+
return if self == buffer
|
26
|
+
|
27
|
+
@current_buffer = buffer.current
|
28
|
+
@buffer_stack = buffer.buffer_stack
|
29
|
+
end
|
30
|
+
|
31
|
+
def append=(arg)
|
32
|
+
@current_buffer.append = arg
|
33
|
+
end
|
34
|
+
|
35
|
+
def safe_append=(arg)
|
36
|
+
@current_buffer.safe_append = arg
|
37
|
+
end
|
38
|
+
|
39
|
+
def safe_concat(arg)
|
40
|
+
# rubocop:disable Rails/OutputSafety
|
41
|
+
@current_buffer.safe_concat(arg)
|
42
|
+
# rubocop:enable Rails/OutputSafety
|
43
|
+
end
|
44
|
+
|
45
|
+
def length
|
46
|
+
@current_buffer.length
|
47
|
+
end
|
48
|
+
|
49
|
+
def push(buffer = nil)
|
50
|
+
buffer ||= self.class.make_frame
|
51
|
+
@buffer_stack.push(buffer)
|
52
|
+
@current_buffer = buffer
|
53
|
+
end
|
54
|
+
|
55
|
+
def pop
|
56
|
+
@buffer_stack.pop.tap do
|
57
|
+
@current_buffer = @buffer_stack.last
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_s
|
62
|
+
@current_buffer
|
63
|
+
end
|
64
|
+
|
65
|
+
alias_method :current, :to_s
|
66
|
+
end
|
67
|
+
end
|
@@ -25,12 +25,19 @@ module ViewComponent
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def register_polymorphic_slot(slot_name, types, collection:)
|
28
|
+
unless types.empty?
|
29
|
+
getter_name = slot_name
|
30
|
+
|
31
|
+
define_method(getter_name) do
|
32
|
+
get_slot(slot_name)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
28
36
|
renderable_hash = types.each_with_object({}) do |(poly_type, poly_callable), memo|
|
29
37
|
memo[poly_type] = define_slot(
|
30
38
|
"#{slot_name}_#{poly_type}", collection: collection, callable: poly_callable
|
31
39
|
)
|
32
40
|
|
33
|
-
getter_name = slot_name
|
34
41
|
setter_name =
|
35
42
|
if collection
|
36
43
|
"#{ActiveSupport::Inflector.singularize(slot_name)}_#{poly_type}"
|
@@ -38,10 +45,6 @@ module ViewComponent
|
|
38
45
|
"#{slot_name}_#{poly_type}"
|
39
46
|
end
|
40
47
|
|
41
|
-
define_method(getter_name) do
|
42
|
-
get_slot(slot_name)
|
43
|
-
end
|
44
|
-
|
45
48
|
define_method(setter_name) do |*args, &block|
|
46
49
|
set_polymorphic_slot(slot_name, poly_type, *args, &block)
|
47
50
|
end
|
@@ -73,8 +73,8 @@ module ViewComponent # :nodoc:
|
|
73
73
|
# Returns the relative path (from preview_path) to the preview example template if the template exists
|
74
74
|
def preview_example_template_path(example)
|
75
75
|
preview_path =
|
76
|
-
Array(preview_paths).detect do |
|
77
|
-
Dir["#{
|
76
|
+
Array(preview_paths).detect do |path|
|
77
|
+
Dir["#{path}/#{preview_name}_preview/#{example}.html.*"].first
|
78
78
|
end
|
79
79
|
|
80
80
|
if preview_path.nil?
|
@@ -45,18 +45,12 @@ module ViewComponent
|
|
45
45
|
if defined?(@__vc_content_set_by_with_content)
|
46
46
|
@__vc_component_instance.with_content(@__vc_content_set_by_with_content)
|
47
47
|
|
48
|
-
view_context
|
49
|
-
@__vc_component_instance.render_in(view_context)
|
50
|
-
end
|
48
|
+
@__vc_component_instance.render_in(view_context)
|
51
49
|
elsif defined?(@__vc_content_block)
|
52
|
-
|
53
|
-
|
54
|
-
@__vc_component_instance.render_in(view_context, &@__vc_content_block)
|
55
|
-
end
|
50
|
+
# render_in is faster than `parent.render`
|
51
|
+
@__vc_component_instance.render_in(view_context, &@__vc_content_block)
|
56
52
|
else
|
57
|
-
view_context
|
58
|
-
@__vc_component_instance.render_in(view_context)
|
59
|
-
end
|
53
|
+
@__vc_component_instance.render_in(view_context)
|
60
54
|
end
|
61
55
|
elsif defined?(@__vc_content)
|
62
56
|
@__vc_content
|
@@ -23,7 +23,7 @@ module ViewComponent
|
|
23
23
|
# class_name: "Header" # class name string, used to instantiate Slot
|
24
24
|
# )
|
25
25
|
def with_slot(*slot_names, collection: false, class_name: nil)
|
26
|
-
|
26
|
+
ViewComponent::Deprecation.warn(
|
27
27
|
"`with_slot` is deprecated and will be removed in ViewComponent v3.0.0.\n" \
|
28
28
|
"Use the new slots API (https://viewcomponent.org/guide/slots.html) instead."
|
29
29
|
)
|
@@ -7,6 +7,11 @@ module ViewComponent
|
|
7
7
|
module SlotableV2
|
8
8
|
extend ActiveSupport::Concern
|
9
9
|
|
10
|
+
RESERVED_NAMES = {
|
11
|
+
singular: %i[content render].freeze,
|
12
|
+
plural: %i[contents renders].freeze,
|
13
|
+
}.freeze
|
14
|
+
|
10
15
|
# Setup component slot state
|
11
16
|
included do
|
12
17
|
# Hash of registered Slots
|
@@ -75,6 +80,10 @@ module ViewComponent
|
|
75
80
|
end
|
76
81
|
ruby2_keywords(slot_name.to_sym) if respond_to?(:ruby2_keywords, true)
|
77
82
|
|
83
|
+
define_method "#{slot_name}?" do
|
84
|
+
get_slot(slot_name).present?
|
85
|
+
end
|
86
|
+
|
78
87
|
register_slot(slot_name, collection: false, callable: callable)
|
79
88
|
end
|
80
89
|
|
@@ -140,6 +149,10 @@ module ViewComponent
|
|
140
149
|
end
|
141
150
|
end
|
142
151
|
|
152
|
+
define_method "#{slot_name}?" do
|
153
|
+
get_slot(slot_name).present?
|
154
|
+
end
|
155
|
+
|
143
156
|
register_slot(slot_name, collection: true, callable: callable)
|
144
157
|
end
|
145
158
|
|
@@ -197,24 +210,26 @@ module ViewComponent
|
|
197
210
|
end
|
198
211
|
|
199
212
|
def validate_plural_slot_name(slot_name)
|
200
|
-
if slot_name.to_sym
|
213
|
+
if RESERVED_NAMES[:plural].include?(slot_name.to_sym)
|
201
214
|
raise ArgumentError.new(
|
202
215
|
"#{self} declares a slot named #{slot_name}, which is a reserved word in the ViewComponent framework.\n\n" \
|
203
216
|
"To fix this issue, choose a different name."
|
204
217
|
)
|
205
218
|
end
|
206
219
|
|
220
|
+
raise_if_slot_ends_with_question_mark(slot_name)
|
207
221
|
raise_if_slot_registered(slot_name)
|
208
222
|
end
|
209
223
|
|
210
224
|
def validate_singular_slot_name(slot_name)
|
211
|
-
if slot_name.to_sym
|
225
|
+
if RESERVED_NAMES[:singular].include?(slot_name.to_sym)
|
212
226
|
raise ArgumentError.new(
|
213
227
|
"#{self} declares a slot named #{slot_name}, which is a reserved word in the ViewComponent framework.\n\n" \
|
214
228
|
"To fix this issue, choose a different name."
|
215
229
|
)
|
216
230
|
end
|
217
231
|
|
232
|
+
raise_if_slot_ends_with_question_mark(slot_name)
|
218
233
|
raise_if_slot_registered(slot_name)
|
219
234
|
end
|
220
235
|
|
@@ -227,6 +242,17 @@ module ViewComponent
|
|
227
242
|
)
|
228
243
|
end
|
229
244
|
end
|
245
|
+
|
246
|
+
def raise_if_slot_ends_with_question_mark(slot_name)
|
247
|
+
if slot_name.to_s.ends_with?("?")
|
248
|
+
raise ArgumentError.new(
|
249
|
+
"#{self} declares a slot named #{slot_name}, which ends with a question mark.\n\n"\
|
250
|
+
"This is not allowed because the ViewComponent framework already provides predicate "\
|
251
|
+
"methods ending in `?`.\n\n" \
|
252
|
+
"To fix this issue, choose a different name."
|
253
|
+
)
|
254
|
+
end
|
255
|
+
end
|
230
256
|
end
|
231
257
|
|
232
258
|
def get_slot(slot_name)
|
@@ -276,8 +302,8 @@ module ViewComponent
|
|
276
302
|
renderable_function = slot_definition[:renderable_function].bind(self)
|
277
303
|
renderable_value =
|
278
304
|
if block_given?
|
279
|
-
renderable_function.call(*args) do |*
|
280
|
-
view_context.capture(*
|
305
|
+
renderable_function.call(*args) do |*rargs|
|
306
|
+
view_context.capture(*rargs, &block)
|
281
307
|
end
|
282
308
|
else
|
283
309
|
renderable_function.call(*args)
|
data/lib/view_component.rb
CHANGED
@@ -10,7 +10,10 @@ module ViewComponent
|
|
10
10
|
autoload :Compiler
|
11
11
|
autoload :CompileCache
|
12
12
|
autoload :ComponentError
|
13
|
+
autoload :Deprecation
|
14
|
+
autoload :GlobalOutputBuffer
|
13
15
|
autoload :Instrumentation
|
16
|
+
autoload :OutputBufferStack
|
14
17
|
autoload :Preview
|
15
18
|
autoload :PreviewTemplateError
|
16
19
|
autoload :TestHelpers
|
@@ -21,8 +24,8 @@ end
|
|
21
24
|
|
22
25
|
# :nocov:
|
23
26
|
if defined?(ViewComponent::Engine)
|
24
|
-
|
25
|
-
"
|
27
|
+
ViewComponent::Deprecation.warn(
|
28
|
+
"Manually loading the engine is deprecated and will be removed in v3.0.0. " \
|
26
29
|
"Remove `require \"view_component/engine\"`."
|
27
30
|
)
|
28
31
|
elsif defined?(Rails::Engine)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: view_component
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.52.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitHub Open Source
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-03-
|
11
|
+
date: 2022-03-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -329,8 +329,13 @@ files:
|
|
329
329
|
- lib/view_component/compiler.rb
|
330
330
|
- lib/view_component/component_error.rb
|
331
331
|
- lib/view_component/content_areas.rb
|
332
|
+
- lib/view_component/deprecation.rb
|
333
|
+
- lib/view_component/docs_builder_component.html.erb
|
334
|
+
- lib/view_component/docs_builder_component.rb
|
332
335
|
- lib/view_component/engine.rb
|
336
|
+
- lib/view_component/global_output_buffer.rb
|
333
337
|
- lib/view_component/instrumentation.rb
|
338
|
+
- lib/view_component/output_buffer_stack.rb
|
334
339
|
- lib/view_component/polymorphic_slots.rb
|
335
340
|
- lib/view_component/preview.rb
|
336
341
|
- lib/view_component/preview_template_error.rb
|