view_component 2.45.0 → 2.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of view_component might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +2 -1
- data/app/controllers/view_components_controller.rb +1 -2
- data/app/helpers/preview_helper.rb +52 -4
- data/app/views/view_components/_preview_source.html.erb +3 -3
- data/app/views/view_components/preview.html.erb +7 -3
- data/docs/CHANGELOG.md +189 -0
- data/lib/rails/generators/abstract_generator.rb +5 -1
- data/lib/rails/generators/component/component_generator.rb +3 -0
- data/lib/rails/generators/component/templates/component.rb.tt +1 -1
- data/lib/rails/generators/locale/component_generator.rb +46 -0
- data/lib/rails/generators/stimulus/component_generator.rb +1 -1
- data/lib/rails/generators/tailwindcss/component_generator.rb +11 -0
- data/lib/rails/generators/tailwindcss/templates/component.html.erb.tt +1 -0
- data/lib/view_component/base.rb +35 -3
- data/lib/view_component/collection.rb +20 -6
- data/lib/view_component/compile_cache.rb +4 -0
- data/lib/view_component/compiler.rb +84 -41
- data/lib/view_component/engine.rb +18 -3
- data/lib/view_component/instrumentation.rb +5 -3
- data/lib/view_component/slot_v2.rb +2 -0
- data/lib/view_component/test_helpers.rb +7 -1
- data/lib/view_component/translatable.rb +16 -12
- data/lib/view_component/version.rb +1 -1
- data/lib/view_component.rb +10 -3
- metadata +24 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8feda954b97fc366da967bee72554a1975a40f50aef0cbd1cde6f83f97547d49
|
4
|
+
data.tar.gz: 92312db0824d58d24ee8f74f56c3a14e961f03fc71bb1669b1a529fedfb225f3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1ff56022e2212300d6ae5f8ea8700364fc5675354476b1830e05885c6f4beff3f7b75d6733531eea10aa1b0830917e025686bf17af89606ed2e457aaf1004f73
|
7
|
+
data.tar.gz: 5d8dddd267a4a627af6ba0a07507d9d0c2dd2ae38da92f542a690e2fa3052bdc56aa37488e1fde440c9a064b825f0ef1db3ec838f06eba4a4598b4e8c95e315f
|
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
|
1
|
+

|
2
|
+

|
2
3
|
|
3
4
|
A framework for building reusable, testable & encapsulated view components in Ruby on Rails.
|
4
5
|
|
@@ -29,12 +29,11 @@ class ViewComponentsController < Rails::ApplicationController # :nodoc:
|
|
29
29
|
@example_name = File.basename(params[:path])
|
30
30
|
@render_args = @preview.render_args(@example_name, params: params.permit!)
|
31
31
|
layout = determine_layout(@render_args[:layout], prepend_views: false)[:layout]
|
32
|
-
template = @render_args[:template]
|
33
32
|
locals = @render_args[:locals]
|
34
33
|
opts = {}
|
35
34
|
opts[:layout] = layout if layout.present? || layout == false
|
36
35
|
opts[:locals] = locals if locals.present?
|
37
|
-
render
|
36
|
+
render "view_components/preview", opts # rubocop:disable GitHub/RailsControllerRenderLiteral
|
38
37
|
end
|
39
38
|
end
|
40
39
|
|
@@ -4,16 +4,64 @@ module PreviewHelper
|
|
4
4
|
AVAILABLE_PRISM_LANGUAGES = ["ruby", "erb", "haml"]
|
5
5
|
FALLBACK_LANGUAGE = "ruby"
|
6
6
|
|
7
|
-
def
|
7
|
+
def preview_source
|
8
|
+
return if @render_args.nil?
|
9
|
+
|
10
|
+
render "preview_source" # rubocop:disable GitHub/RailsViewRenderPathsExist
|
11
|
+
end
|
12
|
+
|
13
|
+
def find_template_data(lookup_context:, template_identifier:)
|
14
|
+
template = lookup_context.find_template(template_identifier)
|
15
|
+
|
16
|
+
if Rails.version.to_f >= 6.1 || template.source.present?
|
17
|
+
return {
|
18
|
+
source: template.source,
|
19
|
+
prism_language_name: prism_language_name_by_template(template: template)
|
20
|
+
}
|
21
|
+
else
|
22
|
+
# Fetch template source via finding it through preview paths
|
23
|
+
# to accomodate source view when exclusively using templates
|
24
|
+
# for previews for Rails < 6.1.
|
25
|
+
all_template_paths = ViewComponent::Base.preview_paths.map do |preview_path|
|
26
|
+
Dir.glob("#{preview_path}/**/*")
|
27
|
+
end.flatten
|
28
|
+
|
29
|
+
# Search for templates the contain `html`.
|
30
|
+
matching_templates = all_template_paths.find_all do |template|
|
31
|
+
template =~ /#{template_identifier}*.(html)/
|
32
|
+
end
|
33
|
+
|
34
|
+
# In-case of a conflict due to multiple template files with
|
35
|
+
# the same name
|
36
|
+
raise "found 0 matches for templates for #{template_identifier}." if matching_templates.empty?
|
37
|
+
raise "found multiple templates for #{template_identifier}." if matching_templates.size > 1
|
38
|
+
|
39
|
+
template_file_path = matching_templates.first
|
40
|
+
template_source = File.read(template_file_path)
|
41
|
+
prism_language_name = prism_language_name_by_template_path(template_file_path: template_file_path)
|
42
|
+
|
43
|
+
return {
|
44
|
+
source: template_source,
|
45
|
+
prism_language_name: prism_language_name
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def prism_language_name_by_template(template:)
|
8
53
|
language = template.identifier.split(".").last
|
54
|
+
|
9
55
|
return FALLBACK_LANGUAGE unless AVAILABLE_PRISM_LANGUAGES.include? language
|
10
56
|
|
11
57
|
language
|
12
58
|
end
|
13
59
|
|
14
|
-
def
|
15
|
-
|
60
|
+
def prism_language_name_by_template_path(template_file_path:)
|
61
|
+
language = template_file_path.gsub(".html", "").split(".").last
|
16
62
|
|
17
|
-
|
63
|
+
return FALLBACK_LANGUAGE unless AVAILABLE_PRISM_LANGUAGES.include? language
|
64
|
+
|
65
|
+
language
|
18
66
|
end
|
19
67
|
end
|
@@ -7,9 +7,9 @@
|
|
7
7
|
<%= h @preview.preview_source(@example_name) %>
|
8
8
|
</code>
|
9
9
|
<% else %>
|
10
|
-
<%
|
11
|
-
<code class="language-<%= prism_language_name
|
12
|
-
<%= h
|
10
|
+
<% template_data = find_template_data(lookup_context: @view_renderer.lookup_context, template_identifier: @render_args[:template]) %>
|
11
|
+
<code class="language-<%= template_data[:prism_language_name] %>">
|
12
|
+
<%= h template_data[:source] %>
|
13
13
|
</code>
|
14
14
|
<% end %>
|
15
15
|
</pre>
|
@@ -1,7 +1,11 @@
|
|
1
|
-
<% if
|
2
|
-
|
1
|
+
<% if @render_args[:component] %>
|
2
|
+
<% if ViewComponent::Base.render_monkey_patch_enabled || Rails.version.to_f >= 6.1 %>
|
3
|
+
<%= render(@render_args[:component], @render_args[:args], &@render_args[:block]) %>
|
4
|
+
<% else %>
|
5
|
+
<%= render_component(@render_args[:component], &@render_args[:block]) %>
|
6
|
+
<% end %>
|
3
7
|
<% else %>
|
4
|
-
<%=
|
8
|
+
<%= render template: @render_args[:template], locals: @render_args[:locals] || {} %>
|
5
9
|
<% end %>
|
6
10
|
|
7
11
|
<% if ViewComponent::Base.show_previews_source %>
|
data/docs/CHANGELOG.md
CHANGED
@@ -7,6 +7,195 @@ title: Changelog
|
|
7
7
|
|
8
8
|
## main
|
9
9
|
|
10
|
+
## 2.49.0
|
11
|
+
|
12
|
+
* Fix path handling for evaluated view components that broke in Ruby 3.1.
|
13
|
+
|
14
|
+
*Adam Hess*
|
15
|
+
|
16
|
+
* Fix support for the `default:` option for a global translation.
|
17
|
+
|
18
|
+
*Elia Schito*
|
19
|
+
|
20
|
+
* Ensure i18n scope is a symbol to protect lookups.
|
21
|
+
|
22
|
+
*Simon Fish*
|
23
|
+
|
24
|
+
* Small update to preview docs to include rspec mention.
|
25
|
+
|
26
|
+
*Leigh Halliday*
|
27
|
+
|
28
|
+
* Small improvements to collection iteration docs.
|
29
|
+
|
30
|
+
*Brian O'Rourke*
|
31
|
+
|
32
|
+
* Add good and bad examples to `ViewComponents in practice`.
|
33
|
+
|
34
|
+
*Joel Hawksley*
|
35
|
+
|
36
|
+
* Add Ruby 3.1 and Rails 7.0 to CI
|
37
|
+
|
38
|
+
*Peter Goldstein*
|
39
|
+
|
40
|
+
## 2.48.0
|
41
|
+
|
42
|
+
* Correct path in example test command in Contributing docs.
|
43
|
+
|
44
|
+
*Mark Wilkinson*
|
45
|
+
|
46
|
+
* Update link to GOV.UK Components library in the resources list.
|
47
|
+
|
48
|
+
*Peter Yates*
|
49
|
+
|
50
|
+
* Add Lookbook to Resources docs page.
|
51
|
+
|
52
|
+
*Mark Perkins*
|
53
|
+
|
54
|
+
* Add blocking compiler mode for use in Rails development and testing modes, improving thread safety.
|
55
|
+
|
56
|
+
*Horia Radu*
|
57
|
+
|
58
|
+
* Add generators to support `tailwindcss-rails`.
|
59
|
+
|
60
|
+
*Dino Maric*, *Hans Lemuet*
|
61
|
+
|
62
|
+
* Add a namespaced component example to docs.
|
63
|
+
|
64
|
+
*Hans Lemuet*
|
65
|
+
|
66
|
+
* Setup `Appraisal` to add flexibility when testing ViewComponent against multiple Rails versions.
|
67
|
+
|
68
|
+
*Hans Lemuet*
|
69
|
+
|
70
|
+
* Return correct values for `request.path` and `request.query_string` methods when using the `with_request_url` test helper.
|
71
|
+
|
72
|
+
*Vasiliy Matyushin*
|
73
|
+
|
74
|
+
* Improve style in generators docs.
|
75
|
+
|
76
|
+
*Hans Lemuet*
|
77
|
+
|
78
|
+
* Correctly type Ruby version strings and update Rails versions used in CI configuration.
|
79
|
+
|
80
|
+
*Hans Lemuet*
|
81
|
+
|
82
|
+
* Make `ViewComponent::Collection` act like a collection of view components.
|
83
|
+
|
84
|
+
*Sammy Henningsson*
|
85
|
+
|
86
|
+
* Update `@param` of `#render_inline` to include `ViewComponent::Collection`.
|
87
|
+
|
88
|
+
*Yutaka Kamei*
|
89
|
+
|
90
|
+
* Add Wecasa to users list.
|
91
|
+
|
92
|
+
*Mohamed Ziata*
|
93
|
+
|
94
|
+
## 2.47.0
|
95
|
+
|
96
|
+
* Display preview source on previews that exclusively use templates.
|
97
|
+
|
98
|
+
*Edwin Mak*
|
99
|
+
|
100
|
+
* Add a test to ensure trailing newlines are stripped when rendering with `#render_in`.
|
101
|
+
|
102
|
+
*Simon Fish*
|
103
|
+
|
104
|
+
* Add WEBrick as a depenency to the application.
|
105
|
+
|
106
|
+
*Connor McQuillan*
|
107
|
+
|
108
|
+
* Update Ruby version in `.tool-versions`.
|
109
|
+
|
110
|
+
*Connor McQuillan*
|
111
|
+
|
112
|
+
* Add a test to ensure blocks can be passed into lambda slots without the need for any other arguments.
|
113
|
+
|
114
|
+
*Simon Fish*
|
115
|
+
|
116
|
+
* Add linters for file consistency.
|
117
|
+
|
118
|
+
*Simon Fish*
|
119
|
+
|
120
|
+
* Add @boardfish to docs/index.md and sort contributors.
|
121
|
+
|
122
|
+
*Simon Fish*
|
123
|
+
|
124
|
+
* Set up Codespaces for bug replication.
|
125
|
+
|
126
|
+
*Simon Fish*
|
127
|
+
|
128
|
+
* Add instructions for replicating bugs and failures.
|
129
|
+
|
130
|
+
*Simon Fish*
|
131
|
+
|
132
|
+
* Make @boardfish a committer.
|
133
|
+
|
134
|
+
*Joel Hawksley*
|
135
|
+
|
136
|
+
* Validate collection parameter with Active Model attribute names.
|
137
|
+
|
138
|
+
*Simon Fish*
|
139
|
+
|
140
|
+
* Fix `helpers` not working with component slots when rendered more than 2 component levels deep.
|
141
|
+
|
142
|
+
*Blake Williams*
|
143
|
+
|
144
|
+
* Update ruby to the latest versions
|
145
|
+
|
146
|
+
*Pedro Paiva*
|
147
|
+
|
148
|
+
* Fix `vale` linter config options.
|
149
|
+
|
150
|
+
*Hans Lemuet*
|
151
|
+
|
152
|
+
* Improve Contributing docs to include how to run tests for a specific version on Rails.
|
153
|
+
|
154
|
+
*Hans Lemuet*
|
155
|
+
|
156
|
+
* Add failing test for default form builder and documentation around known issue.
|
157
|
+
|
158
|
+
*Simon Fish*
|
159
|
+
|
160
|
+
* Add `--locale` flag to the component generator. Generates as many locale files as defined in `I18n.available_locales`, alongside the component.
|
161
|
+
* Add config option `config.view_component.generate_locale` to enable project-wide locale generation.
|
162
|
+
* Add config option `config.view_component.generate_distinct_locale_files` to enable project-wide per-locale translations file generation.
|
163
|
+
|
164
|
+
*Bob Maerten*
|
165
|
+
|
166
|
+
* Add config option `config.view_component.generate_sidecar` to always generate in the sidecar directory.
|
167
|
+
|
168
|
+
*Gleydson Tavares*
|
169
|
+
|
170
|
+
## 2.46.0
|
171
|
+
|
172
|
+
* Add thread safety to the compiler.
|
173
|
+
|
174
|
+
*Horia Radu*
|
175
|
+
|
176
|
+
* Add theme-specific logo images to readme.
|
177
|
+
|
178
|
+
*Dylan Smith*
|
179
|
+
|
180
|
+
* Add Orbit to users list.
|
181
|
+
|
182
|
+
*Nicolas Goutay*
|
183
|
+
|
184
|
+
* Increase clarity around purpose and use of slots.
|
185
|
+
|
186
|
+
*Simon Fish*
|
187
|
+
|
188
|
+
* Deprecate loading `view_component/engine` directly.
|
189
|
+
|
190
|
+
**Upgrade notice**: You should update your `Gemfile` like this:
|
191
|
+
|
192
|
+
```diff
|
193
|
+
- gem "view_component", require: "view_component/engine"`
|
194
|
+
+ gem "view_component"
|
195
|
+
```
|
196
|
+
|
197
|
+
*Yoshiyuki Hirano*
|
198
|
+
|
10
199
|
## 2.45.0
|
11
200
|
|
12
201
|
* Remove internal APIs from API documentation, fix link to license.
|
@@ -15,7 +15,7 @@ module ViewComponent
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def destination_directory
|
18
|
-
if
|
18
|
+
if sidecar?
|
19
19
|
File.join(component_path, class_path, destination_file_name)
|
20
20
|
else
|
21
21
|
File.join(component_path, class_path)
|
@@ -42,5 +42,9 @@ module ViewComponent
|
|
42
42
|
gsub("/", "--")
|
43
43
|
end
|
44
44
|
end
|
45
|
+
|
46
|
+
def sidecar?
|
47
|
+
options["sidecar"] || ViewComponent::Base.generate_sidecar
|
48
|
+
end
|
45
49
|
end
|
46
50
|
end
|
@@ -15,6 +15,7 @@ module Rails
|
|
15
15
|
class_option :parent, type: :string, desc: "The parent class for the generated component"
|
16
16
|
class_option :stimulus, type: :boolean, default: ViewComponent::Base.generate_stimulus_controller
|
17
17
|
class_option :sidecar, type: :boolean, default: false
|
18
|
+
class_option :locale, type: :boolean, default: ViewComponent::Base.generate_locale
|
18
19
|
|
19
20
|
def create_component_file
|
20
21
|
template "component.rb", File.join(component_path, class_path, "#{file_name}_component.rb")
|
@@ -26,6 +27,8 @@ module Rails
|
|
26
27
|
|
27
28
|
hook_for :stimulus, type: :boolean
|
28
29
|
|
30
|
+
hook_for :locale, type: :boolean
|
31
|
+
|
29
32
|
hook_for :template_engine do |instance, template_engine|
|
30
33
|
instance.invoke template_engine, [instance.name]
|
31
34
|
end
|
@@ -6,7 +6,7 @@ class <%= class_name %>Component < <%= parent_class %>
|
|
6
6
|
<%= initialize_body %>
|
7
7
|
end
|
8
8
|
<%- end -%>
|
9
|
-
<%- if initialize_call_method_for_inline? -%>
|
9
|
+
<%- if initialize_call_method_for_inline? -%>
|
10
10
|
def call
|
11
11
|
content_tag :h1, "Hello world!"<%= ", data: { controller: \"#{stimulus_controller}\" }" if options["stimulus"] %>
|
12
12
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators/abstract_generator"
|
4
|
+
|
5
|
+
module Locale
|
6
|
+
module Generators
|
7
|
+
class ComponentGenerator < ::Rails::Generators::NamedBase
|
8
|
+
include ViewComponent::AbstractGenerator
|
9
|
+
|
10
|
+
source_root File.expand_path("templates", __dir__)
|
11
|
+
argument :attributes, type: :array, default: [], banner: "attribute"
|
12
|
+
class_option :sidecar, type: :boolean, default: false
|
13
|
+
|
14
|
+
def create_locale_file
|
15
|
+
if ViewComponent::Base.generate_distinct_locale_files
|
16
|
+
I18n.available_locales.each do |locale|
|
17
|
+
create_file destination(locale), translations_hash([locale]).to_yaml
|
18
|
+
end
|
19
|
+
else
|
20
|
+
create_file destination, translations_hash(I18n.available_locales).to_yaml
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def translations_hash(locales = [:en])
|
27
|
+
locales.map { |locale| [locale.to_s, translation_keys] }.to_h
|
28
|
+
end
|
29
|
+
|
30
|
+
def translation_keys
|
31
|
+
keys = attributes.map(&:name)
|
32
|
+
keys = %w[hello] if keys.empty?
|
33
|
+
keys.map { |name| [name, name.capitalize] }.to_h
|
34
|
+
end
|
35
|
+
|
36
|
+
def destination(locale = nil)
|
37
|
+
extention = ".#{locale}" if locale
|
38
|
+
if sidecar?
|
39
|
+
File.join(component_path, class_path, "#{file_name}_component", "#{file_name}_component#{extention}.yml")
|
40
|
+
else
|
41
|
+
File.join(component_path, class_path, "#{file_name}_component#{extention}.yml")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -23,7 +23,7 @@ module Stimulus
|
|
23
23
|
private
|
24
24
|
|
25
25
|
def destination
|
26
|
-
if
|
26
|
+
if sidecar?
|
27
27
|
File.join(component_path, class_path, "#{file_name}_component", "#{file_name}_component_controller.js")
|
28
28
|
else
|
29
29
|
File.join(component_path, class_path, "#{file_name}_component_controller.js")
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators/erb/component_generator"
|
4
|
+
|
5
|
+
module Tailwindcss
|
6
|
+
module Generators
|
7
|
+
class ComponentGenerator < Erb::Generators::ComponentGenerator
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<div<%= data_attributes %>>Add <%= class_name %> template here</div>
|
data/lib/view_component/base.rb
CHANGED
@@ -279,11 +279,31 @@ module ViewComponent
|
|
279
279
|
#
|
280
280
|
mattr_accessor :generate_stimulus_controller, instance_writer: false, default: false
|
281
281
|
|
282
|
+
# Always generate translations file alongside the component:
|
283
|
+
#
|
284
|
+
# config.view_component.generate_locale = true
|
285
|
+
#
|
286
|
+
# Defaults to `false`.
|
287
|
+
#
|
288
|
+
mattr_accessor :generate_locale, instance_writer: false, default: false
|
289
|
+
|
290
|
+
# Always generate as many translations files as available locales:
|
291
|
+
#
|
292
|
+
# config.view_component.generate_distinct_locale_files = true
|
293
|
+
#
|
294
|
+
# Defaults to `false`.
|
295
|
+
#
|
296
|
+
# One file will be generated for each configured `I18n.available_locales`.
|
297
|
+
# Fallback on `[:en]` when no available_locales is defined.
|
298
|
+
#
|
299
|
+
mattr_accessor :generate_distinct_locale_files, instance_writer: false, default: false
|
300
|
+
|
282
301
|
# Path for component files
|
283
302
|
#
|
284
303
|
# config.view_component.view_component_path = "app/my_components"
|
285
304
|
#
|
286
305
|
# Defaults to `app/components`.
|
306
|
+
#
|
287
307
|
mattr_accessor :view_component_path, instance_writer: false, default: "app/components"
|
288
308
|
|
289
309
|
# Parent class for generated components
|
@@ -291,8 +311,16 @@ module ViewComponent
|
|
291
311
|
# config.view_component.component_parent_class = "MyBaseComponent"
|
292
312
|
#
|
293
313
|
# Defaults to "ApplicationComponent" if defined, "ViewComponent::Base" otherwise.
|
294
|
-
|
295
|
-
|
314
|
+
#
|
315
|
+
mattr_accessor :component_parent_class, instance_writer: false
|
316
|
+
|
317
|
+
# Always generate a component with a sidecar directory:
|
318
|
+
#
|
319
|
+
# config.view_component.generate_sidecar = true
|
320
|
+
#
|
321
|
+
# Defaults to `false`.
|
322
|
+
#
|
323
|
+
mattr_accessor :generate_sidecar, instance_writer: false, default: false
|
296
324
|
|
297
325
|
class << self
|
298
326
|
# @private
|
@@ -373,7 +401,7 @@ module ViewComponent
|
|
373
401
|
# Derive the source location of the component Ruby file from the call stack.
|
374
402
|
# We need to ignore `inherited` frames here as they indicate that `inherited`
|
375
403
|
# has been re-defined by the consuming application, likely in ApplicationComponent.
|
376
|
-
child.source_location = caller_locations(1, 10).reject { |l| l.label == "inherited" }[0].
|
404
|
+
child.source_location = caller_locations(1, 10).reject { |l| l.label == "inherited" }[0].path
|
377
405
|
|
378
406
|
# Removes the first part of the path and the extension.
|
379
407
|
child.virtual_path = child.source_location.gsub(
|
@@ -507,6 +535,10 @@ module ViewComponent
|
|
507
535
|
private
|
508
536
|
|
509
537
|
def initialize_parameter_names
|
538
|
+
return attribute_names.map(&:to_sym) if respond_to?(:attribute_names)
|
539
|
+
|
540
|
+
return attribute_types.keys.map(&:to_sym) if Rails::VERSION::MAJOR <= 5 && respond_to?(:attribute_types)
|
541
|
+
|
510
542
|
initialize_parameters.map(&:last)
|
511
543
|
end
|
512
544
|
|
@@ -4,20 +4,34 @@ require "action_view/renderer/collection_renderer" if Rails.version.to_f >= 6.1
|
|
4
4
|
|
5
5
|
module ViewComponent
|
6
6
|
class Collection
|
7
|
+
include Enumerable
|
7
8
|
attr_reader :component
|
8
9
|
|
9
10
|
delegate :format, to: :component
|
11
|
+
delegate :size, to: :@collection
|
10
12
|
|
11
13
|
def render_in(view_context, &block)
|
14
|
+
components.map do |component|
|
15
|
+
component.render_in(view_context, &block)
|
16
|
+
end.join.html_safe # rubocop:disable Rails/OutputSafety
|
17
|
+
end
|
18
|
+
|
19
|
+
def components
|
20
|
+
return @components if defined? @components
|
21
|
+
|
12
22
|
iterator = ActionView::PartialIteration.new(@collection.size)
|
13
23
|
|
14
24
|
component.validate_collection_parameter!(validate_default: true)
|
15
25
|
|
16
|
-
@collection.map do |item|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
26
|
+
@components = @collection.map do |item|
|
27
|
+
component.new(**component_options(item, iterator)).tap do |component|
|
28
|
+
iterator.iterate!
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def each(&block)
|
34
|
+
components.each(&block)
|
21
35
|
end
|
22
36
|
|
23
37
|
private
|
@@ -42,7 +56,7 @@ module ViewComponent
|
|
42
56
|
def component_options(item, iterator)
|
43
57
|
item_options = { component.collection_parameter => item }
|
44
58
|
item_options[component.collection_counter_parameter] = iterator.index + 1 if component.counter_argument_present?
|
45
|
-
item_options[component.collection_iteration_parameter] = iterator if component.iteration_argument_present?
|
59
|
+
item_options[component.collection_iteration_parameter] = iterator.dup if component.iteration_argument_present?
|
46
60
|
|
47
61
|
@options.merge(item_options)
|
48
62
|
end
|
@@ -2,66 +2,95 @@
|
|
2
2
|
|
3
3
|
module ViewComponent
|
4
4
|
class Compiler
|
5
|
+
# Lock required to be obtained before compiling the component
|
6
|
+
attr_reader :__vc_compiler_lock
|
7
|
+
|
8
|
+
# Compiler mode. Can be either:
|
9
|
+
# * development (a blocking mode which ensures thread safety when redefining the `call` method for components,
|
10
|
+
# default in Rails development and test mode)
|
11
|
+
# * production (a non-blocking mode, default in Rails production mode)
|
12
|
+
DEVELOPMENT_MODE = :development
|
13
|
+
PRODUCTION_MODE = :production
|
14
|
+
|
15
|
+
class_attribute :mode, default: PRODUCTION_MODE
|
16
|
+
|
5
17
|
def initialize(component_class)
|
6
18
|
@component_class = component_class
|
19
|
+
@__vc_compiler_lock = Monitor.new
|
7
20
|
end
|
8
21
|
|
9
22
|
def compiled?
|
10
23
|
CompileCache.compiled?(component_class)
|
11
24
|
end
|
12
25
|
|
26
|
+
def development?
|
27
|
+
self.class.mode == DEVELOPMENT_MODE
|
28
|
+
end
|
29
|
+
|
13
30
|
def compile(raise_errors: false)
|
14
31
|
return if compiled?
|
15
32
|
|
16
|
-
|
33
|
+
with_lock do
|
34
|
+
CompileCache.invalidate_class!(component_class)
|
17
35
|
|
18
|
-
|
19
|
-
raise ViewComponent::ComponentError.new(
|
20
|
-
"#{component_class} implements a reserved method, `#with_content`.\n\n" \
|
21
|
-
"To fix this issue, change the name of the method."
|
22
|
-
)
|
23
|
-
end
|
24
|
-
|
25
|
-
if template_errors.present?
|
26
|
-
raise ViewComponent::TemplateError.new(template_errors) if raise_errors
|
36
|
+
subclass_instance_methods = component_class.instance_methods(false)
|
27
37
|
|
28
|
-
|
29
|
-
|
38
|
+
if subclass_instance_methods.include?(:with_content) && raise_errors
|
39
|
+
raise ViewComponent::ComponentError.new(
|
40
|
+
"#{component_class} implements a reserved method, `#with_content`.\n\n" \
|
41
|
+
"To fix this issue, change the name of the method."
|
42
|
+
)
|
43
|
+
end
|
30
44
|
|
31
|
-
|
32
|
-
|
33
|
-
"`#before_render_check` will be removed in v3.0.0.\n\n" \
|
34
|
-
"To fix this issue, use `#before_render` instead."
|
35
|
-
)
|
36
|
-
end
|
45
|
+
if template_errors.present?
|
46
|
+
raise ViewComponent::TemplateError.new(template_errors) if raise_errors
|
37
47
|
|
38
|
-
|
39
|
-
|
40
|
-
component_class.validate_collection_parameter!
|
41
|
-
end
|
48
|
+
return false
|
49
|
+
end
|
42
50
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
51
|
+
if subclass_instance_methods.include?(:before_render_check)
|
52
|
+
ActiveSupport::Deprecation.warn(
|
53
|
+
"`#before_render_check` will be removed in v3.0.0.\n\n" \
|
54
|
+
"To fix this issue, use `#before_render` instead."
|
55
|
+
)
|
56
|
+
end
|
47
57
|
|
48
|
-
if
|
49
|
-
component_class.
|
58
|
+
if raise_errors
|
59
|
+
component_class.validate_initialization_parameters!
|
60
|
+
component_class.validate_collection_parameter!
|
50
61
|
end
|
51
62
|
|
52
|
-
|
63
|
+
templates.each do |template|
|
64
|
+
# Remove existing compiled template methods,
|
65
|
+
# as Ruby warns when redefining a method.
|
66
|
+
method_name = call_method_name(template[:variant])
|
67
|
+
|
68
|
+
if component_class.instance_methods.include?(method_name.to_sym)
|
69
|
+
component_class.send(:undef_method, method_name.to_sym)
|
70
|
+
end
|
71
|
+
|
72
|
+
component_class.class_eval <<-RUBY, template[:path], -1
|
53
73
|
def #{method_name}
|
54
74
|
@output_buffer = ActionView::OutputBuffer.new
|
55
75
|
#{compiled_template(template[:path])}
|
56
76
|
end
|
57
|
-
|
58
|
-
|
77
|
+
RUBY
|
78
|
+
end
|
79
|
+
|
80
|
+
define_render_template_for
|
59
81
|
|
60
|
-
|
82
|
+
component_class._after_compile
|
61
83
|
|
62
|
-
|
84
|
+
CompileCache.register(component_class)
|
85
|
+
end
|
86
|
+
end
|
63
87
|
|
64
|
-
|
88
|
+
def with_lock(&block)
|
89
|
+
if development?
|
90
|
+
__vc_compiler_lock.synchronize(&block)
|
91
|
+
else
|
92
|
+
block.call
|
93
|
+
end
|
65
94
|
end
|
66
95
|
|
67
96
|
private
|
@@ -77,16 +106,30 @@ module ViewComponent
|
|
77
106
|
"elsif variant.to_sym == :#{variant}\n #{call_method_name(variant)}"
|
78
107
|
end.join("\n")
|
79
108
|
|
80
|
-
|
109
|
+
body = <<-RUBY
|
110
|
+
if variant.nil?
|
111
|
+
call
|
112
|
+
#{variant_elsifs}
|
113
|
+
else
|
114
|
+
call
|
115
|
+
end
|
116
|
+
RUBY
|
117
|
+
|
118
|
+
if development?
|
119
|
+
component_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
81
120
|
def render_template_for(variant = nil)
|
82
|
-
|
83
|
-
|
84
|
-
#{variant_elsifs}
|
85
|
-
else
|
86
|
-
call
|
121
|
+
self.class.compiler.with_lock do
|
122
|
+
#{body}
|
87
123
|
end
|
88
124
|
end
|
89
|
-
|
125
|
+
RUBY
|
126
|
+
else
|
127
|
+
component_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
128
|
+
def render_template_for(variant = nil)
|
129
|
+
#{body}
|
130
|
+
end
|
131
|
+
RUBY
|
132
|
+
end
|
90
133
|
end
|
91
134
|
|
92
135
|
def template_errors
|
@@ -115,6 +115,14 @@ module ViewComponent
|
|
115
115
|
end
|
116
116
|
end
|
117
117
|
|
118
|
+
initializer "compiler mode" do |app|
|
119
|
+
ViewComponent::Compiler.mode = if Rails.env.development? || Rails.env.test?
|
120
|
+
ViewComponent::Compiler::DEVELOPMENT_MODE
|
121
|
+
else
|
122
|
+
ViewComponent::Compiler::PRODUCTION_MODE
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
118
126
|
config.after_initialize do |app|
|
119
127
|
options = app.config.view_component
|
120
128
|
|
@@ -145,6 +153,13 @@ module ViewComponent
|
|
145
153
|
end
|
146
154
|
end
|
147
155
|
|
148
|
-
#
|
149
|
-
|
150
|
-
|
156
|
+
# :nocov:
|
157
|
+
unless defined?(ViewComponent::Base)
|
158
|
+
ActiveSupport::Deprecation.warn(
|
159
|
+
"This manually engine loading is deprecated and will be removed in v3.0.0. " \
|
160
|
+
"Remove `require \"view_component/engine\"`."
|
161
|
+
)
|
162
|
+
|
163
|
+
require "view_component"
|
164
|
+
end
|
165
|
+
# :nocov:
|
@@ -5,14 +5,16 @@ require "active_support/notifications"
|
|
5
5
|
module ViewComponent # :nodoc:
|
6
6
|
module Instrumentation
|
7
7
|
def self.included(mod)
|
8
|
-
mod.prepend(self)
|
8
|
+
mod.prepend(self) unless ancestors.include?(ViewComponent::Instrumentation)
|
9
9
|
end
|
10
10
|
|
11
11
|
def render_in(view_context, &block)
|
12
12
|
ActiveSupport::Notifications.instrument(
|
13
13
|
"!render.view_component",
|
14
|
-
|
15
|
-
|
14
|
+
{
|
15
|
+
name: self.class.name,
|
16
|
+
identifier: self.class.identifier
|
17
|
+
}
|
16
18
|
) do
|
17
19
|
super(view_context, &block)
|
18
20
|
end
|
@@ -40,6 +40,8 @@ module ViewComponent
|
|
40
40
|
|
41
41
|
@content =
|
42
42
|
if defined?(@__vc_component_instance)
|
43
|
+
@__vc_component_instance.__vc_original_view_context = @parent.__vc_original_view_context
|
44
|
+
|
43
45
|
if defined?(@__vc_content_set_by_with_content)
|
44
46
|
@__vc_component_instance.with_content(@__vc_content_set_by_with_content)
|
45
47
|
|
@@ -38,7 +38,7 @@ module ViewComponent
|
|
38
38
|
# assert_text("Hello, World!")
|
39
39
|
# ```
|
40
40
|
#
|
41
|
-
# @param component [ViewComponent::Base] The instance of the component to be rendered.
|
41
|
+
# @param component [ViewComponent::Base, ViewComponent::Collection] The instance of the component to be rendered.
|
42
42
|
# @return [Nokogiri::HTML]
|
43
43
|
def render_inline(component, **args, &block)
|
44
44
|
@rendered_component =
|
@@ -113,16 +113,22 @@ module ViewComponent
|
|
113
113
|
#
|
114
114
|
# @param path [String] The path to set for the current request.
|
115
115
|
def with_request_url(path)
|
116
|
+
old_request_path_info = request.path_info
|
116
117
|
old_request_path_parameters = request.path_parameters
|
117
118
|
old_request_query_parameters = request.query_parameters
|
119
|
+
old_request_query_string = request.query_string
|
118
120
|
old_controller = defined?(@controller) && @controller
|
119
121
|
|
122
|
+
request.path_info = path
|
120
123
|
request.path_parameters = Rails.application.routes.recognize_path(path)
|
121
124
|
request.set_header("action_dispatch.request.query_parameters", Rack::Utils.parse_query(path.split("?")[1]))
|
125
|
+
request.set_header(Rack::QUERY_STRING, path.split("?")[1])
|
122
126
|
yield
|
123
127
|
ensure
|
128
|
+
request.path_info = old_request_path_info
|
124
129
|
request.path_parameters = old_request_path_parameters
|
125
130
|
request.set_header("action_dispatch.request.query_parameters", old_request_query_parameters)
|
131
|
+
request.set_header(Rack::QUERY_STRING, old_request_query_string)
|
126
132
|
@controller = old_controller
|
127
133
|
end
|
128
134
|
|
@@ -41,7 +41,7 @@ module ViewComponent
|
|
41
41
|
EMPTY_HASH = {}.freeze
|
42
42
|
|
43
43
|
def initialize(i18n_scope:, load_paths:)
|
44
|
-
@i18n_scope = i18n_scope.split(".")
|
44
|
+
@i18n_scope = i18n_scope.split(".").map(&:to_sym)
|
45
45
|
@load_paths = load_paths
|
46
46
|
end
|
47
47
|
|
@@ -70,21 +70,25 @@ module ViewComponent
|
|
70
70
|
key = key&.to_s unless key.is_a?(String)
|
71
71
|
key = "#{i18n_scope}#{key}" if key.start_with?(".")
|
72
72
|
|
73
|
-
|
74
|
-
|
75
|
-
|
73
|
+
if key.start_with?(i18n_scope + ".")
|
74
|
+
translated =
|
75
|
+
catch(:exception) do
|
76
|
+
i18n_backend.translate(locale, key, options)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Fallback to the global translations
|
80
|
+
if translated.is_a? ::I18n::MissingTranslation
|
81
|
+
return super(key, locale: locale, **options)
|
76
82
|
end
|
77
83
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
end
|
84
|
+
if HTML_SAFE_TRANSLATION_KEY.match?(key)
|
85
|
+
translated = translated.html_safe # rubocop:disable Rails/OutputSafety
|
86
|
+
end
|
82
87
|
|
83
|
-
|
84
|
-
|
88
|
+
translated
|
89
|
+
else
|
90
|
+
super(key, locale: locale, **options)
|
85
91
|
end
|
86
|
-
|
87
|
-
translated
|
88
92
|
end
|
89
93
|
alias :t :translate
|
90
94
|
|
data/lib/view_component.rb
CHANGED
@@ -19,6 +19,13 @@ module ViewComponent
|
|
19
19
|
autoload :Translatable
|
20
20
|
end
|
21
21
|
|
22
|
-
#
|
23
|
-
|
24
|
-
|
22
|
+
# :nocov:
|
23
|
+
if defined?(ViewComponent::Engine)
|
24
|
+
ActiveSupport::Deprecation.warn(
|
25
|
+
"This manually engine loading is deprecated and will be removed in v3.0.0. " \
|
26
|
+
"Remove `require \"view_component/engine\"`."
|
27
|
+
)
|
28
|
+
elsif defined?(Rails::Engine)
|
29
|
+
require "view_component/engine"
|
30
|
+
end
|
31
|
+
# :nocov:
|
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.49.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitHub Open Source
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-02-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -44,6 +44,20 @@ dependencies:
|
|
44
44
|
- - "~>"
|
45
45
|
- !ruby/object:Gem::Version
|
46
46
|
version: '1.0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: appraisal
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2.4'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '2.4'
|
47
61
|
- !ruby/object:Gem::Dependency
|
48
62
|
name: benchmark-ips
|
49
63
|
requirement: !ruby/object:Gem::Requirement
|
@@ -268,7 +282,7 @@ dependencies:
|
|
268
282
|
- - ">="
|
269
283
|
- !ruby/object:Gem::Version
|
270
284
|
version: '0'
|
271
|
-
description:
|
285
|
+
description:
|
272
286
|
email:
|
273
287
|
- opensource+view_component@github.com
|
274
288
|
executables: []
|
@@ -295,6 +309,7 @@ files:
|
|
295
309
|
- lib/rails/generators/erb/templates/component.html.erb.tt
|
296
310
|
- lib/rails/generators/haml/component_generator.rb
|
297
311
|
- lib/rails/generators/haml/templates/component.html.haml.tt
|
312
|
+
- lib/rails/generators/locale/component_generator.rb
|
298
313
|
- lib/rails/generators/preview/component_generator.rb
|
299
314
|
- lib/rails/generators/preview/templates/component_preview.rb.tt
|
300
315
|
- lib/rails/generators/rspec/component_generator.rb
|
@@ -303,6 +318,8 @@ files:
|
|
303
318
|
- lib/rails/generators/slim/templates/component.html.slim.tt
|
304
319
|
- lib/rails/generators/stimulus/component_generator.rb
|
305
320
|
- lib/rails/generators/stimulus/templates/component_controller.js.tt
|
321
|
+
- lib/rails/generators/tailwindcss/component_generator.rb
|
322
|
+
- lib/rails/generators/tailwindcss/templates/component.html.erb.tt
|
306
323
|
- lib/rails/generators/test_unit/component_generator.rb
|
307
324
|
- lib/rails/generators/test_unit/templates/component_test.rb.tt
|
308
325
|
- lib/view_component.rb
|
@@ -341,7 +358,7 @@ licenses:
|
|
341
358
|
- MIT
|
342
359
|
metadata:
|
343
360
|
allowed_push_host: https://rubygems.org
|
344
|
-
post_install_message:
|
361
|
+
post_install_message:
|
345
362
|
rdoc_options: []
|
346
363
|
require_paths:
|
347
364
|
- lib
|
@@ -356,8 +373,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
356
373
|
- !ruby/object:Gem::Version
|
357
374
|
version: '0'
|
358
375
|
requirements: []
|
359
|
-
rubygems_version: 3.
|
360
|
-
signing_key:
|
376
|
+
rubygems_version: 3.3.3
|
377
|
+
signing_key:
|
361
378
|
specification_version: 4
|
362
379
|
summary: View components for Rails
|
363
380
|
test_files: []
|