view_component 2.26.1 → 2.31.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/CHANGELOG.md +85 -0
- data/README.md +0 -8
- data/lib/rails/generators/preview/component_generator.rb +5 -1
- data/lib/view_component.rb +2 -0
- data/lib/view_component/base.rb +65 -11
- data/lib/view_component/compiler.rb +23 -49
- data/lib/view_component/component_error.rb +6 -0
- data/lib/view_component/engine.rb +1 -1
- data/lib/view_component/slot_v2.rb +26 -9
- data/lib/view_component/slotable.rb +7 -2
- data/lib/view_component/slotable_v2.rb +12 -2
- data/lib/view_component/test_helpers.rb +26 -1
- data/lib/view_component/translatable.rb +95 -0
- data/lib/view_component/version.rb +4 -2
- data/lib/view_component/with_content_helper.rb +15 -0
- metadata +20 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 37bb719bf3f89d9d71ce7879e12a55ccafb0b1705565d4256a0f825b68a24db2
|
4
|
+
data.tar.gz: 5a379783bcf6f170e151fd48f1524e942710b3dfbc5cf40e790812476f303ae0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d0c18928b2cb3e6ae1e9a360eaeb05d278fee96c745bc4b17a0f498787862fb5619bf14165c37489d7d96bd064683fcd614166da3ee0c274453db0b47c270db8
|
7
|
+
data.tar.gz: 5381cd03176cfa7b9df47a86d801d7177c292c0f4460c8df70378e97052993f2bf17c9f96f140290d22d44a3a56794f4e9d9ac1378cb8c9666509d3f2fddac62
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,91 @@
|
|
2
2
|
|
3
3
|
## main
|
4
4
|
|
5
|
+
## 2.31.0
|
6
|
+
|
7
|
+
* Add `#with_content` to allow setting content without a block.
|
8
|
+
|
9
|
+
*Jordan Raine, Manuel Puyol*
|
10
|
+
|
11
|
+
* Add `with_request_url` test helper.
|
12
|
+
|
13
|
+
*Mario Schüttel*
|
14
|
+
|
15
|
+
* Improve feature parity with Rails translations
|
16
|
+
* Don't create a translation backend if the component has no translation file
|
17
|
+
* Mark translation keys ending with `html` as HTML-safe
|
18
|
+
* Always convert keys to String
|
19
|
+
* Support multiple keys
|
20
|
+
|
21
|
+
*Elia Schito*
|
22
|
+
|
23
|
+
* Fix errors on `asset_url` helpers when `asset_host` has no protocol.
|
24
|
+
|
25
|
+
*Elia Schito*
|
26
|
+
|
27
|
+
* Prevent slots from overriding the `#content` method when registering a slot with that name.
|
28
|
+
|
29
|
+
*Blake Williams*
|
30
|
+
|
31
|
+
* Deprecate `with_slot` in favor of the new [slots API](https://viewcomponent.org/guide/slots.html).
|
32
|
+
|
33
|
+
*Manuel Puyol*
|
34
|
+
|
35
|
+
## 2.30.0
|
36
|
+
|
37
|
+
* Deprecate `with_content_areas` in favor of [slots](https://viewcomponent.org/guide/slots.html).
|
38
|
+
|
39
|
+
*Joel Hawksley*
|
40
|
+
|
41
|
+
## 2.29.0
|
42
|
+
|
43
|
+
* Allow Slot lambdas to share data from the parent component and allow chaining on the returned component.
|
44
|
+
|
45
|
+
*Sjors Baltus, Blake Williams*
|
46
|
+
|
47
|
+
* Experimental: Add `ViewComponent::Translatable`
|
48
|
+
* `t` and `translate` now will look first into the sidecar YAML translations file.
|
49
|
+
* `helpers.t` and `I18n.t` still reference the global Rails translation files.
|
50
|
+
* `l` and `localize` will still reference the global Rails translation files.
|
51
|
+
|
52
|
+
*Elia Schito*
|
53
|
+
|
54
|
+
* Fix rendering output of pass through slots when using HAML.
|
55
|
+
|
56
|
+
*Alex Robbin, Blake Williams*
|
57
|
+
|
58
|
+
* Experimental: call `._sidecar_files` to fetch the sidecar files for a given list of extensions, e.g. passing `["yml", "yaml"]`.
|
59
|
+
|
60
|
+
*Elia Schito*
|
61
|
+
|
62
|
+
* Fix bug where a single `jbuilder` template matched multiple template handlers.
|
63
|
+
|
64
|
+
*Niels Slot*
|
65
|
+
|
66
|
+
## 2.28.0
|
67
|
+
|
68
|
+
* Include SlotableV2 by default in Base. **Note:** It's no longer necessary to include `ViewComponent::SlotableV2` to use Slots.
|
69
|
+
|
70
|
+
*Joel Hawksley*
|
71
|
+
|
72
|
+
* Prepend Preview routes instead of appending, accounting for cases where host application has catchall route.
|
73
|
+
|
74
|
+
*Joel Hawksley*
|
75
|
+
|
76
|
+
* Fix bug where blocks passed to lambda slots will render incorrectly in certain situations.
|
77
|
+
|
78
|
+
*Blake Williams*
|
79
|
+
|
80
|
+
## 2.27.0
|
81
|
+
|
82
|
+
* Allow customization of the controller used in component tests.
|
83
|
+
|
84
|
+
*Alex Robbin*
|
85
|
+
|
86
|
+
* Generate preview at overridden path if one exists when using `--preview` flag.
|
87
|
+
|
88
|
+
*Nishiki Liu*
|
89
|
+
|
5
90
|
## 2.26.1
|
6
91
|
|
7
92
|
* Fix bug that raises when trying to use a collection before the component has been compiled.
|
data/README.md
CHANGED
@@ -6,14 +6,6 @@ A framework for building reusable, testable & encapsulated view components in Ru
|
|
6
6
|
|
7
7
|
See [viewcomponent.org](https://viewcomponent.org/) for documentation.
|
8
8
|
|
9
|
-
## Installation
|
10
|
-
|
11
|
-
In `Gemfile`, add:
|
12
|
-
|
13
|
-
```ruby
|
14
|
-
gem "view_component", require: "view_component/engine"
|
15
|
-
```
|
16
|
-
|
17
9
|
## Contributing
|
18
10
|
|
19
11
|
This project is intended to be a safe, welcoming space for collaboration. Contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. We recommend reading the [contributing guide](./CONTRIBUTING.md) as well.
|
@@ -8,7 +8,11 @@ module Preview
|
|
8
8
|
check_class_collision suffix: "ComponentPreview"
|
9
9
|
|
10
10
|
def create_preview_file
|
11
|
-
|
11
|
+
preview_paths = Rails.application.config.view_component.preview_paths
|
12
|
+
return if preview_paths.count > 1
|
13
|
+
|
14
|
+
path_prefix = preview_paths.one? ? preview_paths.first : "test/components/previews"
|
15
|
+
template "component_preview.rb", File.join(path_prefix, class_path, "#{file_name}_component_preview.rb")
|
12
16
|
end
|
13
17
|
|
14
18
|
private
|
data/lib/view_component.rb
CHANGED
data/lib/view_component/base.rb
CHANGED
@@ -7,11 +7,14 @@ require "view_component/compile_cache"
|
|
7
7
|
require "view_component/previewable"
|
8
8
|
require "view_component/slotable"
|
9
9
|
require "view_component/slotable_v2"
|
10
|
+
require "view_component/with_content_helper"
|
10
11
|
|
11
12
|
module ViewComponent
|
12
13
|
class Base < ActionView::Base
|
13
14
|
include ActiveSupport::Configurable
|
14
15
|
include ViewComponent::Previewable
|
16
|
+
include ViewComponent::SlotableV2
|
17
|
+
include ViewComponent::WithContentHelper
|
15
18
|
|
16
19
|
ViewContextCalledBeforeRenderError = Class.new(StandardError)
|
17
20
|
|
@@ -78,6 +81,8 @@ module ViewComponent
|
|
78
81
|
old_current_template = @current_template
|
79
82
|
@current_template = self
|
80
83
|
|
84
|
+
raise ArgumentError.new("Block provided after calling `with_content`. Use one or the other.") if block && defined?(@_content_set_by_with_content)
|
85
|
+
|
81
86
|
@_content_evaluated = false
|
82
87
|
@_render_in_block = block
|
83
88
|
|
@@ -130,7 +135,7 @@ module ViewComponent
|
|
130
135
|
@helpers ||= controller.view_context
|
131
136
|
end
|
132
137
|
|
133
|
-
# Exposes .
|
138
|
+
# Exposes .virtual_path as an instance method
|
134
139
|
def virtual_path
|
135
140
|
self.class.virtual_path
|
136
141
|
end
|
@@ -168,8 +173,6 @@ module ViewComponent
|
|
168
173
|
self
|
169
174
|
end
|
170
175
|
|
171
|
-
private
|
172
|
-
|
173
176
|
# Exposes the current request to the component.
|
174
177
|
# Use sparingly as doing so introduces coupling
|
175
178
|
# that inhibits encapsulation & reuse.
|
@@ -177,6 +180,8 @@ module ViewComponent
|
|
177
180
|
@request ||= controller.request
|
178
181
|
end
|
179
182
|
|
183
|
+
private
|
184
|
+
|
180
185
|
attr_reader :view_context
|
181
186
|
|
182
187
|
def content
|
@@ -185,6 +190,8 @@ module ViewComponent
|
|
185
190
|
|
186
191
|
@_content = if @view_context && @_render_in_block
|
187
192
|
view_context.capture(self, &@_render_in_block)
|
193
|
+
elsif defined?(@_content_set_by_with_content)
|
194
|
+
@_content_set_by_with_content
|
188
195
|
end
|
189
196
|
end
|
190
197
|
|
@@ -193,8 +200,9 @@ module ViewComponent
|
|
193
200
|
end
|
194
201
|
|
195
202
|
# The controller used for testing components.
|
196
|
-
# Defaults to ApplicationController
|
197
|
-
#
|
203
|
+
# Defaults to ApplicationController, but can be configured
|
204
|
+
# on a per-test basis using `with_controller_class`.
|
205
|
+
# This should be set early in the initialization process and should be a string.
|
198
206
|
mattr_accessor :test_controller
|
199
207
|
@@test_controller = "ApplicationController"
|
200
208
|
|
@@ -204,6 +212,47 @@ module ViewComponent
|
|
204
212
|
class << self
|
205
213
|
attr_accessor :source_location, :virtual_path
|
206
214
|
|
215
|
+
# EXPERIMENTAL: This API is experimental and may be removed at any time.
|
216
|
+
# Find sidecar files for the given extensions.
|
217
|
+
#
|
218
|
+
# The provided array of extensions is expected to contain
|
219
|
+
# strings starting without the "dot", example: `["erb", "haml"]`.
|
220
|
+
#
|
221
|
+
# For example, one might collect sidecar CSS files that need to be compiled.
|
222
|
+
def _sidecar_files(extensions)
|
223
|
+
return [] unless source_location
|
224
|
+
|
225
|
+
extensions = extensions.join(",")
|
226
|
+
|
227
|
+
# view files in a directory named like the component
|
228
|
+
directory = File.dirname(source_location)
|
229
|
+
filename = File.basename(source_location, ".rb")
|
230
|
+
component_name = name.demodulize.underscore
|
231
|
+
|
232
|
+
# Add support for nested components defined in the same file.
|
233
|
+
#
|
234
|
+
# e.g.
|
235
|
+
#
|
236
|
+
# class MyComponent < ViewComponent::Base
|
237
|
+
# class MyOtherComponent < ViewComponent::Base
|
238
|
+
# end
|
239
|
+
# end
|
240
|
+
#
|
241
|
+
# Without this, `MyOtherComponent` will not look for `my_component/my_other_component.html.erb`
|
242
|
+
nested_component_files = if name.include?("::") && component_name != filename
|
243
|
+
Dir["#{directory}/#{filename}/#{component_name}.*{#{extensions}}"]
|
244
|
+
else
|
245
|
+
[]
|
246
|
+
end
|
247
|
+
|
248
|
+
# view files in the same directory as the component
|
249
|
+
sidecar_files = Dir["#{directory}/#{component_name}.*{#{extensions}}"]
|
250
|
+
|
251
|
+
sidecar_directory_files = Dir["#{directory}/#{component_name}/#{filename}.*{#{extensions}}"]
|
252
|
+
|
253
|
+
(sidecar_files - [source_location] + sidecar_directory_files + nested_component_files).uniq
|
254
|
+
end
|
255
|
+
|
207
256
|
# Render a component collection.
|
208
257
|
def with_collection(collection, **args)
|
209
258
|
Collection.new(self, collection, **args)
|
@@ -237,7 +286,7 @@ module ViewComponent
|
|
237
286
|
end
|
238
287
|
|
239
288
|
def compiled?
|
240
|
-
|
289
|
+
compiler.compiled?
|
241
290
|
end
|
242
291
|
|
243
292
|
# Compile templates to instance methods, assuming they haven't been compiled already.
|
@@ -245,11 +294,11 @@ module ViewComponent
|
|
245
294
|
# Do as much work as possible in this step, as doing so reduces the amount
|
246
295
|
# of work done each time a component is rendered.
|
247
296
|
def compile(raise_errors: false)
|
248
|
-
|
297
|
+
compiler.compile(raise_errors: raise_errors)
|
249
298
|
end
|
250
299
|
|
251
|
-
def
|
252
|
-
@
|
300
|
+
def compiler
|
301
|
+
@_compiler ||= Compiler.new(self)
|
253
302
|
end
|
254
303
|
|
255
304
|
# we'll eventually want to update this to support other types
|
@@ -266,6 +315,11 @@ module ViewComponent
|
|
266
315
|
end
|
267
316
|
|
268
317
|
def with_content_areas(*areas)
|
318
|
+
ActiveSupport::Deprecation.warn(
|
319
|
+
"`with_content_areas` is deprecated and will be removed in ViewComponent v3.0.0.\n" \
|
320
|
+
"Use slots (https://viewcomponent.org/guide/slots.html) instead."
|
321
|
+
)
|
322
|
+
|
269
323
|
if areas.include?(:content)
|
270
324
|
raise ArgumentError.new ":content is a reserved content area name. Please use another name, such as ':body'"
|
271
325
|
end
|
@@ -317,7 +371,7 @@ module ViewComponent
|
|
317
371
|
def validate_initialization_parameters!
|
318
372
|
return unless initialize_parameter_names.include?(RESERVED_PARAMETER)
|
319
373
|
|
320
|
-
raise
|
374
|
+
raise ViewComponent::ComponentError.new(
|
321
375
|
"#{self} initializer cannot contain " \
|
322
376
|
"`#{RESERVED_PARAMETER}` since it will override a " \
|
323
377
|
"public ViewComponent method."
|
@@ -337,7 +391,7 @@ module ViewComponent
|
|
337
391
|
end
|
338
392
|
|
339
393
|
def counter_argument_present?
|
340
|
-
|
394
|
+
initialize_parameter_names.include?(collection_counter_parameter)
|
341
395
|
end
|
342
396
|
|
343
397
|
private
|
@@ -13,17 +13,23 @@ module ViewComponent
|
|
13
13
|
def compile(raise_errors: false)
|
14
14
|
return if compiled?
|
15
15
|
|
16
|
-
|
17
|
-
raise ViewComponent::TemplateError.new(template_errors) if raise_errors
|
18
|
-
return false
|
19
|
-
end
|
16
|
+
subclass_instance_methods = component_class.instance_methods(false)
|
20
17
|
|
21
|
-
if
|
18
|
+
if subclass_instance_methods.include?(:before_render_check)
|
22
19
|
ActiveSupport::Deprecation.warn(
|
23
20
|
"`before_render_check` will be removed in v3.0.0. Use `before_render` instead."
|
24
21
|
)
|
25
22
|
end
|
26
23
|
|
24
|
+
if subclass_instance_methods.include?(:with_content) && raise_errors
|
25
|
+
raise ViewComponent::ComponentError.new("#{component_class} implements a reserved method, `with_content`.")
|
26
|
+
end
|
27
|
+
|
28
|
+
if template_errors.present?
|
29
|
+
raise ViewComponent::TemplateError.new(template_errors) if raise_errors
|
30
|
+
return false
|
31
|
+
end
|
32
|
+
|
27
33
|
if raise_errors
|
28
34
|
component_class.validate_initialization_parameters!
|
29
35
|
component_class.validate_collection_parameter!
|
@@ -61,7 +67,7 @@ module ViewComponent
|
|
61
67
|
"elsif variant.to_sym == :#{variant}\n #{call_method_name(variant)}"
|
62
68
|
end.join("\n")
|
63
69
|
|
64
|
-
component_class.class_eval <<-RUBY
|
70
|
+
component_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
65
71
|
def render_template_for(variant = nil)
|
66
72
|
if variant.nil?
|
67
73
|
call
|
@@ -114,50 +120,18 @@ module ViewComponent
|
|
114
120
|
end
|
115
121
|
|
116
122
|
def templates
|
117
|
-
@templates ||=
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
def matching_views_in_source_location
|
129
|
-
source_location = component_class.source_location
|
130
|
-
return [] unless source_location
|
131
|
-
|
132
|
-
extensions = ActionView::Template.template_handler_extensions.join(",")
|
133
|
-
|
134
|
-
# view files in a directory named like the component
|
135
|
-
directory = File.dirname(source_location)
|
136
|
-
filename = File.basename(source_location, ".rb")
|
137
|
-
component_name = component_class.name.demodulize.underscore
|
138
|
-
|
139
|
-
# Add support for nested components defined in the same file.
|
140
|
-
#
|
141
|
-
# e.g.
|
142
|
-
#
|
143
|
-
# class MyComponent < ViewComponent::Base
|
144
|
-
# class MyOtherComponent < ViewComponent::Base
|
145
|
-
# end
|
146
|
-
# end
|
147
|
-
#
|
148
|
-
# Without this, `MyOtherComponent` will not look for `my_component/my_other_component.html.erb`
|
149
|
-
nested_component_files = if component_class.name.include?("::") && component_name != filename
|
150
|
-
Dir["#{directory}/#{filename}/#{component_name}.*{#{extensions}}"]
|
151
|
-
else
|
152
|
-
[]
|
123
|
+
@templates ||= begin
|
124
|
+
extensions = ActionView::Template.template_handler_extensions
|
125
|
+
|
126
|
+
component_class._sidecar_files(extensions).each_with_object([]) do |path, memo|
|
127
|
+
pieces = File.basename(path).split(".")
|
128
|
+
memo << {
|
129
|
+
path: path,
|
130
|
+
variant: pieces.second.split("+").second&.to_sym,
|
131
|
+
handler: pieces.last
|
132
|
+
}
|
133
|
+
end
|
153
134
|
end
|
154
|
-
|
155
|
-
# view files in the same directory as the component
|
156
|
-
sidecar_files = Dir["#{directory}/#{component_name}.*{#{extensions}}"]
|
157
|
-
|
158
|
-
sidecar_directory_files = Dir["#{directory}/#{component_name}/#{filename}.*{#{extensions}}"]
|
159
|
-
|
160
|
-
(sidecar_files - [source_location] + sidecar_directory_files + nested_component_files)
|
161
135
|
end
|
162
136
|
|
163
137
|
def inline_calls
|
@@ -90,7 +90,7 @@ module ViewComponent
|
|
90
90
|
options = app.config.view_component
|
91
91
|
|
92
92
|
if options.show_previews
|
93
|
-
app.routes.
|
93
|
+
app.routes.prepend do
|
94
94
|
preview_controller = options.preview_controller.sub(/Controller$/, "").underscore
|
95
95
|
|
96
96
|
get options.preview_route, to: "#{preview_controller}#index", as: :preview_view_components, internal: true
|
@@ -1,7 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "view_component/with_content_helper"
|
4
|
+
|
3
5
|
module ViewComponent
|
4
6
|
class SlotV2
|
7
|
+
include ViewComponent::WithContentHelper
|
8
|
+
|
5
9
|
attr_writer :_component_instance, :_content_block, :_content
|
6
10
|
|
7
11
|
def initialize(parent)
|
@@ -25,19 +29,32 @@ module ViewComponent
|
|
25
29
|
return @content if defined?(@content)
|
26
30
|
|
27
31
|
view_context = @parent.send(:view_context)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
32
|
+
|
33
|
+
raise ArgumentError.new("Block provided after calling `with_content`. Use one or the other.") if defined?(@_content_block) && defined?(@_content_set_by_with_content)
|
34
|
+
|
35
|
+
@content = if defined?(@_component_instance)
|
36
|
+
if defined?(@_content_set_by_with_content)
|
37
|
+
@_component_instance.with_content(@_content_set_by_with_content)
|
38
|
+
|
39
|
+
view_context.capture do
|
34
40
|
@_component_instance.render_in(view_context)
|
35
41
|
end
|
36
|
-
elsif defined?(@_content)
|
37
|
-
@_content
|
38
42
|
elsif defined?(@_content_block)
|
39
|
-
|
43
|
+
view_context.capture do
|
44
|
+
# render_in is faster than `parent.render`
|
45
|
+
@_component_instance.render_in(view_context, &@_content_block)
|
46
|
+
end
|
47
|
+
else
|
48
|
+
view_context.capture do
|
49
|
+
@_component_instance.render_in(view_context)
|
50
|
+
end
|
40
51
|
end
|
52
|
+
elsif defined?(@_content)
|
53
|
+
@_content
|
54
|
+
elsif defined?(@_content_block)
|
55
|
+
view_context.capture(&@_content_block)
|
56
|
+
elsif defined?(@_content_set_by_with_content)
|
57
|
+
@_content_set_by_with_content
|
41
58
|
end
|
42
59
|
|
43
60
|
@content
|
@@ -23,6 +23,11 @@ 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
|
+
ActiveSupport::Deprecation.warn(
|
27
|
+
"`with_slot` is deprecated and will be removed in ViewComponent v3.0.0.\n" \
|
28
|
+
"Use the new slots API (https://viewcomponent.org/guide/slots.html) instead."
|
29
|
+
)
|
30
|
+
|
26
31
|
slot_names.each do |slot_name|
|
27
32
|
# Ensure slot_name is not already declared
|
28
33
|
if self.slots.key?(slot_name)
|
@@ -49,14 +54,14 @@ module ViewComponent
|
|
49
54
|
|
50
55
|
# If the slot is a collection, define an accesor that defaults to an empty array
|
51
56
|
if collection
|
52
|
-
class_eval <<-RUBY
|
57
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
53
58
|
def #{accessor_name}
|
54
59
|
content unless content_evaluated? # ensure content is loaded so slots will be defined
|
55
60
|
#{instance_variable_name} ||= []
|
56
61
|
end
|
57
62
|
RUBY
|
58
63
|
else
|
59
|
-
class_eval <<-RUBY
|
64
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
60
65
|
def #{accessor_name}
|
61
66
|
content unless content_evaluated? # ensure content is loaded so slots will be defined
|
62
67
|
#{instance_variable_name} if defined?(#{instance_variable_name})
|
@@ -175,6 +175,10 @@ module ViewComponent
|
|
175
175
|
end
|
176
176
|
|
177
177
|
def validate_slot_name(slot_name)
|
178
|
+
if slot_name.to_sym == :content
|
179
|
+
raise ArgumentError.new("#{slot_name} is not a valid slot name.")
|
180
|
+
end
|
181
|
+
|
178
182
|
if self.registered_slots.key?(slot_name)
|
179
183
|
# TODO remove? This breaks overriding slots when slots are inherited
|
180
184
|
raise ArgumentError.new("#{slot_name} slot declared multiple times")
|
@@ -226,7 +230,13 @@ module ViewComponent
|
|
226
230
|
# Use `bind(self)` to ensure lambda is executed in the context of the
|
227
231
|
# current component. This is necessary to allow the lambda to access helper
|
228
232
|
# methods like `content_tag` as well as parent component state.
|
229
|
-
renderable_value =
|
233
|
+
renderable_value = if block_given?
|
234
|
+
slot_definition[:renderable_function].bind(self).call(*args, **kwargs) do |*args, **kwargs|
|
235
|
+
view_context.capture(*args, **kwargs, &block)
|
236
|
+
end
|
237
|
+
else
|
238
|
+
slot_definition[:renderable_function].bind(self).call(*args, **kwargs)
|
239
|
+
end
|
230
240
|
|
231
241
|
# Function calls can return components, so if it's a component handle it specially
|
232
242
|
if renderable_value.respond_to?(:render_in)
|
@@ -245,7 +255,7 @@ module ViewComponent
|
|
245
255
|
@_set_slots[slot_name] = slot
|
246
256
|
end
|
247
257
|
|
248
|
-
|
258
|
+
slot
|
249
259
|
end
|
250
260
|
end
|
251
261
|
end
|
@@ -35,7 +35,7 @@ module ViewComponent
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def controller
|
38
|
-
@controller ||= Base.test_controller.constantize
|
38
|
+
@controller ||= build_controller(Base.test_controller.constantize)
|
39
39
|
end
|
40
40
|
|
41
41
|
def request
|
@@ -47,7 +47,32 @@ module ViewComponent
|
|
47
47
|
|
48
48
|
controller.view_context.lookup_context.variants = variant
|
49
49
|
yield
|
50
|
+
ensure
|
50
51
|
controller.view_context.lookup_context.variants = old_variants
|
51
52
|
end
|
53
|
+
|
54
|
+
def with_controller_class(klass)
|
55
|
+
old_controller = defined?(@controller) && @controller
|
56
|
+
|
57
|
+
@controller = build_controller(klass)
|
58
|
+
yield
|
59
|
+
ensure
|
60
|
+
@controller = old_controller
|
61
|
+
end
|
62
|
+
|
63
|
+
def with_request_url(path)
|
64
|
+
old_request_path_parameters = request.path_parameters
|
65
|
+
old_controller = defined?(@controller) && @controller
|
66
|
+
|
67
|
+
request.path_parameters = Rails.application.routes.recognize_path(path)
|
68
|
+
yield
|
69
|
+
ensure
|
70
|
+
request.path_parameters = old_request_path_parameters
|
71
|
+
@controller = old_controller
|
72
|
+
end
|
73
|
+
|
74
|
+
def build_controller(klass)
|
75
|
+
klass.new.tap { |c| c.request = request }.extend(Rails.application.routes.url_helpers)
|
76
|
+
end
|
52
77
|
end
|
53
78
|
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "set"
|
4
|
+
require "i18n"
|
5
|
+
require "action_view/helpers/translation_helper"
|
6
|
+
require "active_support/concern"
|
7
|
+
|
8
|
+
module ViewComponent
|
9
|
+
module Translatable
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
HTML_SAFE_TRANSLATION_KEY = /(?:_|\b)html\z/.freeze
|
13
|
+
|
14
|
+
included do
|
15
|
+
class_attribute :i18n_backend, instance_writer: false, instance_predicate: false
|
16
|
+
end
|
17
|
+
|
18
|
+
class_methods do
|
19
|
+
def i18n_scope
|
20
|
+
@i18n_scope ||= virtual_path.sub(%r{^/}, "").gsub(%r{/_?}, ".")
|
21
|
+
end
|
22
|
+
|
23
|
+
def _after_compile
|
24
|
+
super
|
25
|
+
|
26
|
+
return if CompileCache.compiled? self
|
27
|
+
|
28
|
+
if (translation_files = _sidecar_files(%w[yml yaml])).any?
|
29
|
+
self.i18n_backend = I18nBackend.new(
|
30
|
+
i18n_scope: i18n_scope,
|
31
|
+
load_paths: translation_files,
|
32
|
+
)
|
33
|
+
else
|
34
|
+
# Cleanup if translations file has been removed since the last compilation
|
35
|
+
self.i18n_backend = nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class I18nBackend < ::I18n::Backend::Simple
|
41
|
+
EMPTY_HASH = {}.freeze
|
42
|
+
|
43
|
+
def initialize(i18n_scope:, load_paths:)
|
44
|
+
@i18n_scope = i18n_scope.split(".")
|
45
|
+
@load_paths = load_paths
|
46
|
+
end
|
47
|
+
|
48
|
+
# Ensure the Simple backend won't load paths from ::I18n.load_path
|
49
|
+
def load_translations
|
50
|
+
super(@load_paths)
|
51
|
+
end
|
52
|
+
|
53
|
+
def scope_data(data)
|
54
|
+
@i18n_scope.reverse_each do |part|
|
55
|
+
data = { part => data}
|
56
|
+
end
|
57
|
+
data
|
58
|
+
end
|
59
|
+
|
60
|
+
def store_translations(locale, data, options = EMPTY_HASH)
|
61
|
+
super(locale, scope_data(data), options)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def translate(key = nil, **options)
|
66
|
+
return super unless i18n_backend
|
67
|
+
return key.map { |k| translate(k, **options) } if key.is_a?(Array)
|
68
|
+
|
69
|
+
locale = options.delete(:locale) || ::I18n.locale
|
70
|
+
key = key&.to_s unless key.is_a?(String)
|
71
|
+
key = "#{i18n_scope}#{key}" if key.start_with?(".")
|
72
|
+
|
73
|
+
translated = catch(:exception) do
|
74
|
+
i18n_backend.translate(locale, key, options)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Fallback to the global translations
|
78
|
+
if translated.is_a? ::I18n::MissingTranslation
|
79
|
+
return super(key, locale: locale, **options)
|
80
|
+
end
|
81
|
+
|
82
|
+
if HTML_SAFE_TRANSLATION_KEY.match?(key)
|
83
|
+
translated = translated.html_safe
|
84
|
+
end
|
85
|
+
|
86
|
+
translated
|
87
|
+
end
|
88
|
+
alias :t :translate
|
89
|
+
|
90
|
+
# Exposes .i18n_scope as an instance method
|
91
|
+
def i18n_scope
|
92
|
+
self.class.i18n_scope
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
module WithContentHelper
|
5
|
+
def with_content(value)
|
6
|
+
if value.nil?
|
7
|
+
raise ArgumentError.new("No content provided.")
|
8
|
+
else
|
9
|
+
@_content_set_by_with_content = value
|
10
|
+
end
|
11
|
+
|
12
|
+
self
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
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.31.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: 2021-
|
11
|
+
date: 2021-04-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -128,6 +128,20 @@ dependencies:
|
|
128
128
|
- - "~>"
|
129
129
|
- !ruby/object:Gem::Version
|
130
130
|
version: '1'
|
131
|
+
- !ruby/object:Gem::Dependency
|
132
|
+
name: jbuilder
|
133
|
+
requirement: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - "~>"
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '2'
|
138
|
+
type: :development
|
139
|
+
prerelease: false
|
140
|
+
version_requirements: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - "~>"
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '2'
|
131
145
|
- !ruby/object:Gem::Dependency
|
132
146
|
name: rubocop
|
133
147
|
requirement: !ruby/object:Gem::Requirement
|
@@ -232,6 +246,7 @@ files:
|
|
232
246
|
- lib/view_component/collection.rb
|
233
247
|
- lib/view_component/compile_cache.rb
|
234
248
|
- lib/view_component/compiler.rb
|
249
|
+
- lib/view_component/component_error.rb
|
235
250
|
- lib/view_component/engine.rb
|
236
251
|
- lib/view_component/preview.rb
|
237
252
|
- lib/view_component/preview_template_error.rb
|
@@ -249,7 +264,9 @@ files:
|
|
249
264
|
- lib/view_component/template_error.rb
|
250
265
|
- lib/view_component/test_case.rb
|
251
266
|
- lib/view_component/test_helpers.rb
|
267
|
+
- lib/view_component/translatable.rb
|
252
268
|
- lib/view_component/version.rb
|
269
|
+
- lib/view_component/with_content_helper.rb
|
253
270
|
homepage: https://github.com/github/view_component
|
254
271
|
licenses:
|
255
272
|
- MIT
|
@@ -270,7 +287,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
270
287
|
- !ruby/object:Gem::Version
|
271
288
|
version: '0'
|
272
289
|
requirements: []
|
273
|
-
rubygems_version: 3.
|
290
|
+
rubygems_version: 3.1.2
|
274
291
|
signing_key:
|
275
292
|
specification_version: 4
|
276
293
|
summary: View components for Rails
|