view_component 2.50.0 → 2.69.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/LICENSE.txt +1 -1
- data/app/assets/vendor/prism.css +3 -195
- data/app/assets/vendor/prism.min.js +11 -11
- data/app/controllers/concerns/view_component/preview_actions.rb +97 -0
- data/app/controllers/view_components_controller.rb +1 -87
- data/app/helpers/preview_helper.rb +5 -5
- data/app/views/view_components/preview.html.erb +2 -2
- data/docs/CHANGELOG.md +427 -1
- data/lib/rails/generators/abstract_generator.rb +7 -9
- data/lib/rails/generators/component/component_generator.rb +5 -4
- data/lib/rails/generators/locale/component_generator.rb +1 -1
- data/lib/rails/generators/preview/component_generator.rb +1 -1
- data/lib/view_component/base.rb +152 -51
- data/lib/view_component/collection.rb +9 -2
- data/lib/view_component/compiler.rb +39 -18
- data/lib/view_component/config.rb +159 -0
- data/lib/view_component/content_areas.rb +1 -1
- data/lib/view_component/docs_builder_component.rb +1 -1
- data/lib/view_component/engine.rb +16 -30
- data/lib/view_component/polymorphic_slots.rb +28 -1
- data/lib/view_component/preview.rb +12 -9
- data/lib/view_component/render_component_helper.rb +1 -0
- data/lib/view_component/render_component_to_string_helper.rb +1 -1
- data/lib/view_component/render_to_string_monkey_patch.rb +1 -1
- data/lib/view_component/rendering_component_helper.rb +1 -1
- data/lib/view_component/rendering_monkey_patch.rb +1 -1
- data/lib/view_component/slot_v2.rb +4 -10
- data/lib/view_component/slotable.rb +5 -6
- data/lib/view_component/slotable_v2.rb +69 -21
- data/lib/view_component/test_helpers.rb +80 -8
- data/lib/view_component/translatable.rb +13 -14
- data/lib/view_component/version.rb +1 -1
- data/lib/view_component.rb +1 -0
- metadata +47 -18
- data/lib/view_component/previewable.rb +0 -62
@@ -0,0 +1,159 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "view_component/deprecation"
|
4
|
+
|
5
|
+
module ViewComponent
|
6
|
+
class Config
|
7
|
+
class << self
|
8
|
+
# `new` without any arguments initializes the default configuration, but
|
9
|
+
# it's important to differentiate in case that's no longer the case in
|
10
|
+
# future.
|
11
|
+
alias_method :default, :new
|
12
|
+
|
13
|
+
def defaults
|
14
|
+
ActiveSupport::OrderedOptions.new.merge!({
|
15
|
+
generate: ActiveSupport::OrderedOptions.new(false),
|
16
|
+
preview_controller: "ViewComponentsController",
|
17
|
+
preview_route: "/rails/view_components",
|
18
|
+
show_previews_source: false,
|
19
|
+
instrumentation_enabled: false,
|
20
|
+
render_monkey_patch_enabled: true,
|
21
|
+
view_component_path: "app/components",
|
22
|
+
component_parent_class: nil,
|
23
|
+
show_previews: Rails.env.development? || Rails.env.test?,
|
24
|
+
preview_paths: default_preview_paths,
|
25
|
+
test_controller: "ApplicationController",
|
26
|
+
default_preview_layout: nil
|
27
|
+
})
|
28
|
+
end
|
29
|
+
|
30
|
+
# @!attribute generate
|
31
|
+
# @return [ActiveSupport::OrderedOptions]
|
32
|
+
# The subset of configuration options relating to generators.
|
33
|
+
#
|
34
|
+
# All options under this namespace default to `false` unless otherwise
|
35
|
+
# stated.
|
36
|
+
#
|
37
|
+
# #### #sidecar
|
38
|
+
#
|
39
|
+
# Always generate a component with a sidecar directory:
|
40
|
+
#
|
41
|
+
# config.view_component.generate.sidecar = true
|
42
|
+
#
|
43
|
+
# #### #stimulus_controller
|
44
|
+
#
|
45
|
+
# Always generate a Stimulus controller alongside the component:
|
46
|
+
#
|
47
|
+
# config.view_component.generate.stimulus_controller = true
|
48
|
+
#
|
49
|
+
# #### #locale
|
50
|
+
#
|
51
|
+
# Always generate translations file alongside the component:
|
52
|
+
#
|
53
|
+
# config.view_component.generate.locale = true
|
54
|
+
#
|
55
|
+
# #### #distinct_locale_files
|
56
|
+
#
|
57
|
+
# Always generate as many translations files as available locales:
|
58
|
+
#
|
59
|
+
# config.view_component.generate.distinct_locale_files = true
|
60
|
+
#
|
61
|
+
# One file will be generated for each configured `I18n.available_locales`,
|
62
|
+
# falling back to `[:en]` when no `available_locales` is defined.
|
63
|
+
#
|
64
|
+
# #### #preview
|
65
|
+
#
|
66
|
+
# Always generate a preview alongside the component:
|
67
|
+
#
|
68
|
+
# config.view_component.generate.preview = true
|
69
|
+
|
70
|
+
# @!attribute preview_controller
|
71
|
+
# @return [String]
|
72
|
+
# The controller used for previewing components.
|
73
|
+
# Defaults to `ViewComponentsController`.
|
74
|
+
|
75
|
+
# @!attribute preview_route
|
76
|
+
# @return [String]
|
77
|
+
# The entry route for component previews.
|
78
|
+
# Defaults to `"/rails/view_components"`.
|
79
|
+
|
80
|
+
# @!attribute show_previews_source
|
81
|
+
# @return [Boolean]
|
82
|
+
# Whether to display source code previews in component previews.
|
83
|
+
# Defaults to `false`.
|
84
|
+
|
85
|
+
# @!attribute instrumentation_enabled
|
86
|
+
# @return [Boolean]
|
87
|
+
# Whether ActiveSupport notifications are enabled.
|
88
|
+
# Defaults to `false`.
|
89
|
+
|
90
|
+
# @!attribute render_monkey_patch_enabled
|
91
|
+
# @return [Boolean] Whether the #render method should be monkey patched.
|
92
|
+
# If this is disabled, use `#render_component` or
|
93
|
+
# `#render_component_to_string` instead.
|
94
|
+
# Defaults to `true`.
|
95
|
+
|
96
|
+
# @!attribute view_component_path
|
97
|
+
# @return [String]
|
98
|
+
# The path in which components, their templates, and their sidecars should
|
99
|
+
# be stored.
|
100
|
+
# Defaults to `"app/components"`.
|
101
|
+
|
102
|
+
# @!attribute component_parent_class
|
103
|
+
# @return [String]
|
104
|
+
# The parent class from which generated components will inherit.
|
105
|
+
# Defaults to `nil`. If this is falsy, generators will use
|
106
|
+
# `"ApplicationComponent"` if defined, `"ViewComponent::Base"` otherwise.
|
107
|
+
|
108
|
+
# @!attribute show_previews
|
109
|
+
# @return [Boolean]
|
110
|
+
# Whether component previews are enabled.
|
111
|
+
# Defaults to `true` in development and test environments.
|
112
|
+
|
113
|
+
# @!attribute preview_paths
|
114
|
+
# @return [Array<String>]
|
115
|
+
# The locations in which component previews will be looked up.
|
116
|
+
# Defaults to `['test/component/previews']` relative to your Rails root.
|
117
|
+
|
118
|
+
# @!attribute preview_path
|
119
|
+
# @deprecated Use #preview_paths instead. Will be removed in v3.0.0.
|
120
|
+
|
121
|
+
# @!attribute test_controller
|
122
|
+
# @return [String]
|
123
|
+
# The controller used for testing components.
|
124
|
+
# Can also be configured on a per-test basis using `#with_controller_class`.
|
125
|
+
# Defaults to `ApplicationController`.
|
126
|
+
|
127
|
+
# @!attribute default_preview_layout
|
128
|
+
# @return [String]
|
129
|
+
# A custom default layout used for the previews index page and individual
|
130
|
+
# previews.
|
131
|
+
# Defaults to `nil`. If this is falsy, `"component_preview"` is used.
|
132
|
+
|
133
|
+
def default_preview_paths
|
134
|
+
return [] unless defined?(Rails.root) && Dir.exist?("#{Rails.root}/test/components/previews")
|
135
|
+
|
136
|
+
["#{Rails.root}/test/components/previews"]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def initialize
|
141
|
+
@config = self.class.defaults
|
142
|
+
end
|
143
|
+
|
144
|
+
def preview_path
|
145
|
+
preview_paths
|
146
|
+
end
|
147
|
+
|
148
|
+
def preview_path=(new_value)
|
149
|
+
ViewComponent::Deprecation.warn("`preview_path` will be removed in v3.0.0. Use `preview_paths` instead.")
|
150
|
+
self.preview_paths = Array.wrap(new_value)
|
151
|
+
end
|
152
|
+
|
153
|
+
delegate_missing_to :config
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
attr_reader :config
|
158
|
+
end
|
159
|
+
end
|
@@ -28,7 +28,7 @@ module ViewComponent
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def types
|
31
|
-
" → [#{@method.tag(:return).types.join(
|
31
|
+
" → [#{@method.tag(:return).types.join(",")}]" if @method.tag(:return)&.types && show_types?
|
32
32
|
end
|
33
33
|
|
34
34
|
def signature_or_name
|
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "rails"
|
4
|
+
require "view_component/base"
|
4
5
|
|
5
6
|
module ViewComponent
|
6
7
|
class Engine < Rails::Engine # :nodoc:
|
7
|
-
config.view_component =
|
8
|
-
config.view_component.preview_paths ||= []
|
8
|
+
config.view_component = ViewComponent::Base.config
|
9
9
|
|
10
10
|
rake_tasks do
|
11
11
|
load "view_component/rails/tasks/view_component.rake"
|
@@ -14,25 +14,20 @@ module ViewComponent
|
|
14
14
|
initializer "view_component.set_configs" do |app|
|
15
15
|
options = app.config.view_component
|
16
16
|
|
17
|
+
%i[generate preview_controller preview_route show_previews_source].each do |config_option|
|
18
|
+
options[config_option] ||= ViewComponent::Base.public_send(config_option)
|
19
|
+
end
|
20
|
+
options.instrumentation_enabled = false if options.instrumentation_enabled.nil?
|
17
21
|
options.render_monkey_patch_enabled = true if options.render_monkey_patch_enabled.nil?
|
18
22
|
options.show_previews = Rails.env.development? || Rails.env.test? if options.show_previews.nil?
|
19
|
-
options.show_previews_source ||= ViewComponent::Base.show_previews_source
|
20
23
|
options.instrumentation_enabled = false if options.instrumentation_enabled.nil?
|
21
|
-
options.preview_route ||= ViewComponent::Base.preview_route
|
22
|
-
options.preview_controller ||= ViewComponent::Base.preview_controller
|
23
24
|
|
24
25
|
if options.show_previews
|
26
|
+
# This is still necessary because when `config.view_component` is declared, `Rails.root` is unspecified.
|
25
27
|
options.preview_paths << "#{Rails.root}/test/components/previews" if defined?(Rails.root) && Dir.exist?(
|
26
28
|
"#{Rails.root}/test/components/previews"
|
27
29
|
)
|
28
30
|
|
29
|
-
if options.preview_path.present?
|
30
|
-
ViewComponent::Deprecation.warn(
|
31
|
-
"`preview_path` will be removed in v3.0.0. Use `preview_paths` instead."
|
32
|
-
)
|
33
|
-
options.preview_paths << options.preview_path
|
34
|
-
end
|
35
|
-
|
36
31
|
if options.show_previews_source
|
37
32
|
require "method_source"
|
38
33
|
|
@@ -41,10 +36,6 @@ module ViewComponent
|
|
41
36
|
end
|
42
37
|
end
|
43
38
|
end
|
44
|
-
|
45
|
-
ActiveSupport.on_load(:view_component) do
|
46
|
-
options.each { |k, v| send("#{k}=", v) if respond_to?("#{k}=") }
|
47
|
-
end
|
48
39
|
end
|
49
40
|
|
50
41
|
initializer "view_component.enable_instrumentation" do |app|
|
@@ -61,7 +52,8 @@ module ViewComponent
|
|
61
52
|
options = app.config.view_component
|
62
53
|
|
63
54
|
if options.show_previews && !options.preview_paths.empty?
|
64
|
-
ActiveSupport::Dependencies.autoload_paths
|
55
|
+
paths_to_add = options.preview_paths - ActiveSupport::Dependencies.autoload_paths
|
56
|
+
ActiveSupport::Dependencies.autoload_paths.concat(paths_to_add) if paths_to_add.any?
|
65
57
|
end
|
66
58
|
end
|
67
59
|
|
@@ -71,12 +63,6 @@ module ViewComponent
|
|
71
63
|
end
|
72
64
|
end
|
73
65
|
|
74
|
-
initializer "view_component.compile_config_methods" do
|
75
|
-
ActiveSupport.on_load(:view_component) do
|
76
|
-
config.compile_methods! if config.respond_to?(:compile_methods!)
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
66
|
initializer "view_component.monkey_patch_render" do |app|
|
81
67
|
next if Rails.version.to_f >= 6.1 || !app.config.view_component.render_monkey_patch_enabled
|
82
68
|
|
@@ -93,7 +79,7 @@ module ViewComponent
|
|
93
79
|
end
|
94
80
|
end
|
95
81
|
|
96
|
-
initializer "view_component.include_render_component" do |
|
82
|
+
initializer "view_component.include_render_component" do |_app|
|
97
83
|
next if Rails.version.to_f >= 6.1
|
98
84
|
|
99
85
|
ActiveSupport.on_load(:action_view) do
|
@@ -115,12 +101,12 @@ module ViewComponent
|
|
115
101
|
end
|
116
102
|
end
|
117
103
|
|
118
|
-
initializer "compiler mode" do |
|
104
|
+
initializer "compiler mode" do |_app|
|
119
105
|
ViewComponent::Compiler.mode = if Rails.env.development? || Rails.env.test?
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
106
|
+
ViewComponent::Compiler::DEVELOPMENT_MODE
|
107
|
+
else
|
108
|
+
ViewComponent::Compiler::PRODUCTION_MODE
|
109
|
+
end
|
124
110
|
end
|
125
111
|
|
126
112
|
config.after_initialize do |app|
|
@@ -159,7 +145,7 @@ unless defined?(ViewComponent::Base)
|
|
159
145
|
|
160
146
|
ViewComponent::Deprecation.warn(
|
161
147
|
"This manually engine loading is deprecated and will be removed in v3.0.0. " \
|
162
|
-
|
148
|
+
'Remove `require "view_component/engine"`.'
|
163
149
|
)
|
164
150
|
|
165
151
|
require "view_component"
|
@@ -5,6 +5,17 @@ module ViewComponent
|
|
5
5
|
# In older rails versions, using a concern isn't a good idea here because they appear to not work with
|
6
6
|
# Module#prepend and class methods.
|
7
7
|
def self.included(base)
|
8
|
+
if base != ViewComponent::Base
|
9
|
+
# :nocov:
|
10
|
+
location = Kernel.caller_locations(1, 1)[0]
|
11
|
+
|
12
|
+
warn(
|
13
|
+
"warning: ViewComponent::PolymorphicSlots is now included in ViewComponent::Base by default " \
|
14
|
+
"and can be removed from #{location.path}:#{location.lineno}"
|
15
|
+
)
|
16
|
+
# :nocov:
|
17
|
+
end
|
18
|
+
|
8
19
|
base.singleton_class.prepend(ClassMethods)
|
9
20
|
base.include(InstanceMethods)
|
10
21
|
end
|
@@ -31,6 +42,10 @@ module ViewComponent
|
|
31
42
|
define_method(getter_name) do
|
32
43
|
get_slot(slot_name)
|
33
44
|
end
|
45
|
+
|
46
|
+
define_method("#{getter_name}?") do
|
47
|
+
get_slot(slot_name).present?
|
48
|
+
end
|
34
49
|
end
|
35
50
|
|
36
51
|
renderable_hash = types.each_with_object({}) do |(poly_type, poly_callable), memo|
|
@@ -46,12 +61,24 @@ module ViewComponent
|
|
46
61
|
end
|
47
62
|
|
48
63
|
define_method(setter_name) do |*args, &block|
|
64
|
+
if _warn_on_deprecated_slot_setter
|
65
|
+
ViewComponent::Deprecation.warn(
|
66
|
+
"polymorphic slot setters like `#{setter_name}` are deprecated and will be removed in " \
|
67
|
+
"ViewComponent v3.0.0.\n\nUse `with_#{setter_name}` instead."
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
49
71
|
set_polymorphic_slot(slot_name, poly_type, *args, &block)
|
50
72
|
end
|
51
73
|
ruby2_keywords(setter_name.to_sym) if respond_to?(:ruby2_keywords, true)
|
74
|
+
|
75
|
+
define_method("with_#{setter_name}") do |*args, &block|
|
76
|
+
set_polymorphic_slot(slot_name, poly_type, *args, &block)
|
77
|
+
end
|
78
|
+
ruby2_keywords(:"with_#{setter_name}") if respond_to?(:ruby2_keywords, true)
|
52
79
|
end
|
53
80
|
|
54
|
-
|
81
|
+
registered_slots[slot_name] = {
|
55
82
|
collection: collection,
|
56
83
|
renderable_hash: renderable_hash
|
57
84
|
}
|
@@ -14,7 +14,7 @@ module ViewComponent # :nodoc:
|
|
14
14
|
block: block,
|
15
15
|
component: component,
|
16
16
|
locals: {},
|
17
|
-
template: "view_components/preview"
|
17
|
+
template: "view_components/preview"
|
18
18
|
}
|
19
19
|
end
|
20
20
|
|
@@ -30,7 +30,8 @@ module ViewComponent # :nodoc:
|
|
30
30
|
class << self
|
31
31
|
# Returns all component preview classes.
|
32
32
|
def all
|
33
|
-
load_previews
|
33
|
+
load_previews
|
34
|
+
|
34
35
|
descendants
|
35
36
|
end
|
36
37
|
|
@@ -65,10 +66,12 @@ module ViewComponent # :nodoc:
|
|
65
66
|
name.chomp("Preview").underscore
|
66
67
|
end
|
67
68
|
|
69
|
+
# rubocop:disable Style/TrivialAccessors
|
68
70
|
# Setter for layout name.
|
69
71
|
def layout(layout_name)
|
70
72
|
@layout = layout_name
|
71
73
|
end
|
74
|
+
# rubocop:enable Style/TrivialAccessors
|
72
75
|
|
73
76
|
# Returns the relative path (from preview_path) to the preview example template if the template exists
|
74
77
|
def preview_example_template_path(example)
|
@@ -86,26 +89,26 @@ module ViewComponent # :nodoc:
|
|
86
89
|
end
|
87
90
|
|
88
91
|
path = Dir["#{preview_path}/#{preview_name}_preview/#{example}.html.*"].first
|
89
|
-
Pathname.new(path)
|
90
|
-
relative_path_from(Pathname.new(preview_path))
|
91
|
-
to_s
|
92
|
-
sub(/\..*$/, "")
|
92
|
+
Pathname.new(path)
|
93
|
+
.relative_path_from(Pathname.new(preview_path))
|
94
|
+
.to_s
|
95
|
+
.sub(/\..*$/, "")
|
93
96
|
end
|
94
97
|
|
95
98
|
# Returns the method body for the example from the preview file.
|
96
99
|
def preview_source(example)
|
97
|
-
source =
|
100
|
+
source = instance_method(example.to_sym).source.split("\n")
|
98
101
|
source[1...(source.size - 1)].join("\n")
|
99
102
|
end
|
100
103
|
|
101
|
-
private
|
102
|
-
|
103
104
|
def load_previews
|
104
105
|
Array(preview_paths).each do |preview_path|
|
105
106
|
Dir["#{preview_path}/**/*_preview.rb"].sort.each { |file| require_dependency file }
|
106
107
|
end
|
107
108
|
end
|
108
109
|
|
110
|
+
private
|
111
|
+
|
109
112
|
def preview_paths
|
110
113
|
Base.preview_paths
|
111
114
|
end
|
@@ -4,7 +4,7 @@ module ViewComponent
|
|
4
4
|
module RenderingMonkeyPatch # :nodoc:
|
5
5
|
def render(options = {}, args = {})
|
6
6
|
if options.respond_to?(:render_in)
|
7
|
-
self.response_body = options.render_in(
|
7
|
+
self.response_body = options.render_in(view_context)
|
8
8
|
else
|
9
9
|
super
|
10
10
|
end
|
@@ -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
|
@@ -30,7 +30,7 @@ module ViewComponent
|
|
30
30
|
|
31
31
|
slot_names.each do |slot_name|
|
32
32
|
# Ensure slot_name isn't already declared
|
33
|
-
if
|
33
|
+
if slots.key?(slot_name)
|
34
34
|
raise ArgumentError.new("#{slot_name} slot declared multiple times")
|
35
35
|
end
|
36
36
|
|
@@ -73,7 +73,7 @@ module ViewComponent
|
|
73
73
|
class_name = "ViewComponent::Slot" unless class_name.present?
|
74
74
|
|
75
75
|
# Register the slot on the component
|
76
|
-
|
76
|
+
slots[slot_name] = {
|
77
77
|
class_name: class_name,
|
78
78
|
instance_variable_name: instance_variable_name,
|
79
79
|
collection: collection
|
@@ -84,7 +84,7 @@ module ViewComponent
|
|
84
84
|
def inherited(child)
|
85
85
|
# Clone slot configuration into child class
|
86
86
|
# see #test_slots_pollution
|
87
|
-
child.slots =
|
87
|
+
child.slots = slots.clone
|
88
88
|
|
89
89
|
super
|
90
90
|
end
|
@@ -106,7 +106,7 @@ module ViewComponent
|
|
106
106
|
#
|
107
107
|
def slot(slot_name, **args, &block)
|
108
108
|
# Raise ArgumentError if `slot` doesn't exist
|
109
|
-
unless slots.
|
109
|
+
unless slots.key?(slot_name)
|
110
110
|
raise ArgumentError.new "Unknown slot '#{slot_name}' - expected one of '#{slots.keys}'"
|
111
111
|
end
|
112
112
|
|
@@ -123,8 +123,7 @@ module ViewComponent
|
|
123
123
|
slot_instance = args.present? ? slot_class.new(**args) : slot_class.new
|
124
124
|
|
125
125
|
# Capture block and assign to slot_instance#content
|
126
|
-
|
127
|
-
slot_instance.content = view_context.capture(&block).to_s.strip.html_safe if block_given?
|
126
|
+
slot_instance.content = view_context.capture(&block).to_s.strip.html_safe if block
|
128
127
|
|
129
128
|
if slot[:collection]
|
130
129
|
# Initialize instance variable as an empty array
|