view_component 2.49.1 → 2.52.0
Sign up to get free protection for your applications and to get access to all the features.
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
|