view_component 2.28.0 → 2.32.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 +95 -0
- data/README.md +0 -8
- data/lib/view_component.rb +2 -0
- data/lib/view_component/base.rb +97 -14
- data/lib/view_component/compiler.rb +18 -44
- 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 +5 -0
- data/lib/view_component/slotable_v2.rb +12 -6
- data/lib/view_component/test_helpers.rb +17 -1
- data/lib/view_component/translatable.rb +95 -0
- data/lib/view_component/version.rb +1 -1
- data/lib/view_component/with_content_helper.rb +15 -0
- metadata +38 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0cdb5a57cbe401fab359e9daedfcb0180b2e6ec07bd332e7616c6a611442df13
|
4
|
+
data.tar.gz: 11ec2515ac60b615adc2a52cc7b526c7632871363a24a7b3128ca9a4873d57a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ba44487de1f76102ec0fa9ea4fdd4af85c079c665c0e879855af5fe57577878a176badfef0af2a3d06d3384e852986048ab1332ed0a78fc589adea83f087b037
|
7
|
+
data.tar.gz: 32b565ca3c4759b366acf47de7f42a74ec52adb96d951013759cb5a289ffc16956b432bbee8e0dd79c3291c16386655fd7b8332da2ad3ba29db9dadcc87456a9
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,101 @@
|
|
2
2
|
|
3
3
|
## main
|
4
4
|
|
5
|
+
## 2.32.0
|
6
|
+
|
7
|
+
* Enable previews by default in test environment.
|
8
|
+
|
9
|
+
*Edouard Piron*
|
10
|
+
|
11
|
+
* Fix test helper compatibility with Rails 7.0, TestRequest, and TestSession.
|
12
|
+
|
13
|
+
*Leo Correa*
|
14
|
+
|
15
|
+
* Add experimental `_output_postamble` lifecyle method.
|
16
|
+
|
17
|
+
*Joel Hawksley*
|
18
|
+
|
19
|
+
* Add compatibility notes on FAQ.
|
20
|
+
|
21
|
+
*Matheus Richard*
|
22
|
+
|
23
|
+
* Add Bridgetown on Compatibility documentation.
|
24
|
+
|
25
|
+
*Matheus Richard*
|
26
|
+
|
27
|
+
* Are you interested in building the future of ViewComponent? GitHub is looking to hire a Senior Engineer to work on Primer ViewComponents and ViewComponent. Apply here: [US/Canada](https://github.com/careers) / [Europe](https://boards.greenhouse.io/github/jobs/3132294). Feel free to reach out to joelhawksley@github.com with any questions.
|
28
|
+
|
29
|
+
*Joel Hawksley*
|
30
|
+
|
31
|
+
## 2.31.1
|
32
|
+
|
33
|
+
* Fix `DEPRECATION WARNING: before_render_check` when compiling `ViewComponent::Base`
|
34
|
+
|
35
|
+
*Dave Kroondyk*
|
36
|
+
|
37
|
+
## 2.31.0
|
38
|
+
|
39
|
+
_Note: This release includes an underlying change to Slots that may affect incorrect usage of the API, where Slots were set on a line prefixed by `<%=`. The result of setting a Slot should not be returned. (`<%`)_
|
40
|
+
|
41
|
+
* Add `#with_content` to allow setting content without a block.
|
42
|
+
|
43
|
+
*Jordan Raine, Manuel Puyol*
|
44
|
+
|
45
|
+
* Add `with_request_url` test helper.
|
46
|
+
|
47
|
+
*Mario Schüttel*
|
48
|
+
|
49
|
+
* Improve feature parity with Rails translations
|
50
|
+
* Don't create a translation backend if the component has no translation file
|
51
|
+
* Mark translation keys ending with `html` as HTML-safe
|
52
|
+
* Always convert keys to String
|
53
|
+
* Support multiple keys
|
54
|
+
|
55
|
+
*Elia Schito*
|
56
|
+
|
57
|
+
* Fix errors on `asset_url` helpers when `asset_host` has no protocol.
|
58
|
+
|
59
|
+
*Elia Schito*
|
60
|
+
|
61
|
+
* Prevent slots from overriding the `#content` method when registering a slot with that name.
|
62
|
+
|
63
|
+
*Blake Williams*
|
64
|
+
|
65
|
+
* Deprecate `with_slot` in favor of the new [slots API](https://viewcomponent.org/guide/slots.html).
|
66
|
+
|
67
|
+
*Manuel Puyol*
|
68
|
+
|
69
|
+
## 2.30.0
|
70
|
+
|
71
|
+
* Deprecate `with_content_areas` in favor of [slots](https://viewcomponent.org/guide/slots.html).
|
72
|
+
|
73
|
+
*Joel Hawksley*
|
74
|
+
|
75
|
+
## 2.29.0
|
76
|
+
|
77
|
+
* Allow Slot lambdas to share data from the parent component and allow chaining on the returned component.
|
78
|
+
|
79
|
+
*Sjors Baltus, Blake Williams*
|
80
|
+
|
81
|
+
* Experimental: Add `ViewComponent::Translatable`
|
82
|
+
* `t` and `translate` now will look first into the sidecar YAML translations file.
|
83
|
+
* `helpers.t` and `I18n.t` still reference the global Rails translation files.
|
84
|
+
* `l` and `localize` will still reference the global Rails translation files.
|
85
|
+
|
86
|
+
*Elia Schito*
|
87
|
+
|
88
|
+
* Fix rendering output of pass through slots when using HAML.
|
89
|
+
|
90
|
+
*Alex Robbin, Blake Williams*
|
91
|
+
|
92
|
+
* Experimental: call `._sidecar_files` to fetch the sidecar files for a given list of extensions, e.g. passing `["yml", "yaml"]`.
|
93
|
+
|
94
|
+
*Elia Schito*
|
95
|
+
|
96
|
+
* Fix bug where a single `jbuilder` template matched multiple template handlers.
|
97
|
+
|
98
|
+
*Niels Slot*
|
99
|
+
|
5
100
|
## 2.28.0
|
6
101
|
|
7
102
|
* Include SlotableV2 by default in Base. **Note:** It's no longer necessary to include `ViewComponent::SlotableV2` to use Slots.
|
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.
|
data/lib/view_component.rb
CHANGED
data/lib/view_component/base.rb
CHANGED
@@ -7,12 +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
|
15
16
|
include ViewComponent::SlotableV2
|
17
|
+
include ViewComponent::WithContentHelper
|
16
18
|
|
17
19
|
ViewContextCalledBeforeRenderError = Class.new(StandardError)
|
18
20
|
|
@@ -28,6 +30,7 @@ module ViewComponent
|
|
28
30
|
# Hook for allowing components to do work as part of the compilation process.
|
29
31
|
#
|
30
32
|
# For example, one might compile component-specific assets at this point.
|
33
|
+
# @private TODO: add documentation
|
31
34
|
def self._after_compile
|
32
35
|
# noop
|
33
36
|
end
|
@@ -56,6 +59,7 @@ module ViewComponent
|
|
56
59
|
# returns:
|
57
60
|
# <span title="greeting">Hello, world!</span>
|
58
61
|
#
|
62
|
+
# @private
|
59
63
|
def render_in(view_context, &block)
|
60
64
|
self.class.compile(raise_errors: true)
|
61
65
|
|
@@ -79,13 +83,15 @@ module ViewComponent
|
|
79
83
|
old_current_template = @current_template
|
80
84
|
@current_template = self
|
81
85
|
|
86
|
+
raise ArgumentError.new("Block provided after calling `with_content`. Use one or the other.") if block && defined?(@_content_set_by_with_content)
|
87
|
+
|
82
88
|
@_content_evaluated = false
|
83
89
|
@_render_in_block = block
|
84
90
|
|
85
91
|
before_render
|
86
92
|
|
87
93
|
if render?
|
88
|
-
render_template_for(@variant)
|
94
|
+
render_template_for(@variant) + _output_postamble
|
89
95
|
else
|
90
96
|
""
|
91
97
|
end
|
@@ -93,18 +99,36 @@ module ViewComponent
|
|
93
99
|
@current_template = old_current_template
|
94
100
|
end
|
95
101
|
|
102
|
+
# EXPERIMENTAL: Optional content to be returned after the rendered template.
|
103
|
+
#
|
104
|
+
# @return [String]
|
105
|
+
def _output_postamble
|
106
|
+
""
|
107
|
+
end
|
108
|
+
|
109
|
+
# Called before rendering the component. Override to perform operations that depend on having access to the view context, such as helpers.
|
110
|
+
#
|
111
|
+
# @return [void]
|
96
112
|
def before_render
|
97
113
|
before_render_check
|
98
114
|
end
|
99
115
|
|
116
|
+
# Called after rendering the component.
|
117
|
+
#
|
118
|
+
# @deprecated Use `before_render` instead. Will be removed in v3.0.0.
|
119
|
+
# @return [void]
|
100
120
|
def before_render_check
|
101
121
|
# noop
|
102
122
|
end
|
103
123
|
|
124
|
+
# Override to determine whether the ViewComponent should render.
|
125
|
+
#
|
126
|
+
# @return [Boolean]
|
104
127
|
def render?
|
105
128
|
true
|
106
129
|
end
|
107
130
|
|
131
|
+
# @private
|
108
132
|
def initialize(*); end
|
109
133
|
|
110
134
|
# Re-use original view_context if we're not rendering a component.
|
@@ -112,6 +136,7 @@ module ViewComponent
|
|
112
136
|
# This prevents an exception when rendering a partial inside of a component that has also been rendered outside
|
113
137
|
# of the component. This is due to the partials compiled template method existing in the parent `view_context`,
|
114
138
|
# and not the component's `view_context`.
|
139
|
+
# @private
|
115
140
|
def render(options = {}, args = {}, &block)
|
116
141
|
if options.is_a? ViewComponent::Base
|
117
142
|
super
|
@@ -120,28 +145,36 @@ module ViewComponent
|
|
120
145
|
end
|
121
146
|
end
|
122
147
|
|
148
|
+
# The current controller. Use sparingly as doing so introduces coupling that inhibits encapsulation & reuse, often making testing difficult.
|
149
|
+
#
|
150
|
+
# @return [ActionController::Base]
|
123
151
|
def controller
|
124
152
|
raise ViewContextCalledBeforeRenderError, "`controller` can only be called at render time." if view_context.nil?
|
125
153
|
@controller ||= view_context.controller
|
126
154
|
end
|
127
155
|
|
128
|
-
#
|
156
|
+
# A proxy through which to access helpers. Use sparingly as doing so introduces coupling that inhibits encapsulation & reuse, often making testing difficult.
|
157
|
+
#
|
158
|
+
# @return [ActionView::Base]
|
129
159
|
def helpers
|
130
160
|
raise ViewContextCalledBeforeRenderError, "`helpers` can only be called at render time." if view_context.nil?
|
131
161
|
@helpers ||= controller.view_context
|
132
162
|
end
|
133
163
|
|
134
|
-
# Exposes .
|
164
|
+
# Exposes .virtual_path as an instance method
|
165
|
+
# @private
|
135
166
|
def virtual_path
|
136
167
|
self.class.virtual_path
|
137
168
|
end
|
138
169
|
|
139
170
|
# For caching, such as #cache_if
|
171
|
+
# @private
|
140
172
|
def view_cache_dependencies
|
141
173
|
[]
|
142
174
|
end
|
143
175
|
|
144
176
|
# For caching, such as #cache_if
|
177
|
+
# @private
|
145
178
|
def format
|
146
179
|
# Ruby 2.6 throws a warning without checking `defined?`, 2.7 does not
|
147
180
|
if defined?(@variant)
|
@@ -150,6 +183,7 @@ module ViewComponent
|
|
150
183
|
end
|
151
184
|
|
152
185
|
# Assign the provided content to the content area accessor
|
186
|
+
# @private
|
153
187
|
def with(area, content = nil, &block)
|
154
188
|
unless content_areas.include?(area)
|
155
189
|
raise ArgumentError.new "Unknown content_area '#{area}' - expected one of '#{content_areas}'"
|
@@ -163,21 +197,22 @@ module ViewComponent
|
|
163
197
|
nil
|
164
198
|
end
|
165
199
|
|
200
|
+
# @private TODO: add documentation
|
166
201
|
def with_variant(variant)
|
167
202
|
@variant = variant
|
168
203
|
|
169
204
|
self
|
170
205
|
end
|
171
206
|
|
172
|
-
|
173
|
-
|
174
|
-
#
|
175
|
-
# Use sparingly as doing so introduces coupling
|
176
|
-
# that inhibits encapsulation & reuse.
|
207
|
+
# The current request. Use sparingly as doing so introduces coupling that inhibits encapsulation & reuse, often making testing difficult.
|
208
|
+
#
|
209
|
+
# @return [ActionDispatch::Request]
|
177
210
|
def request
|
178
211
|
@request ||= controller.request
|
179
212
|
end
|
180
213
|
|
214
|
+
private
|
215
|
+
|
181
216
|
attr_reader :view_context
|
182
217
|
|
183
218
|
def content
|
@@ -186,6 +221,8 @@ module ViewComponent
|
|
186
221
|
|
187
222
|
@_content = if @view_context && @_render_in_block
|
188
223
|
view_context.capture(self, &@_render_in_block)
|
224
|
+
elsif defined?(@_content_set_by_with_content)
|
225
|
+
@_content_set_by_with_content
|
189
226
|
end
|
190
227
|
end
|
191
228
|
|
@@ -206,6 +243,47 @@ module ViewComponent
|
|
206
243
|
class << self
|
207
244
|
attr_accessor :source_location, :virtual_path
|
208
245
|
|
246
|
+
# EXPERIMENTAL: This API is experimental and may be removed at any time.
|
247
|
+
# Find sidecar files for the given extensions.
|
248
|
+
#
|
249
|
+
# The provided array of extensions is expected to contain
|
250
|
+
# strings starting without the "dot", example: `["erb", "haml"]`.
|
251
|
+
#
|
252
|
+
# For example, one might collect sidecar CSS files that need to be compiled.
|
253
|
+
def _sidecar_files(extensions)
|
254
|
+
return [] unless source_location
|
255
|
+
|
256
|
+
extensions = extensions.join(",")
|
257
|
+
|
258
|
+
# view files in a directory named like the component
|
259
|
+
directory = File.dirname(source_location)
|
260
|
+
filename = File.basename(source_location, ".rb")
|
261
|
+
component_name = name.demodulize.underscore
|
262
|
+
|
263
|
+
# Add support for nested components defined in the same file.
|
264
|
+
#
|
265
|
+
# e.g.
|
266
|
+
#
|
267
|
+
# class MyComponent < ViewComponent::Base
|
268
|
+
# class MyOtherComponent < ViewComponent::Base
|
269
|
+
# end
|
270
|
+
# end
|
271
|
+
#
|
272
|
+
# Without this, `MyOtherComponent` will not look for `my_component/my_other_component.html.erb`
|
273
|
+
nested_component_files = if name.include?("::") && component_name != filename
|
274
|
+
Dir["#{directory}/#{filename}/#{component_name}.*{#{extensions}}"]
|
275
|
+
else
|
276
|
+
[]
|
277
|
+
end
|
278
|
+
|
279
|
+
# view files in the same directory as the component
|
280
|
+
sidecar_files = Dir["#{directory}/#{component_name}.*{#{extensions}}"]
|
281
|
+
|
282
|
+
sidecar_directory_files = Dir["#{directory}/#{component_name}/#{filename}.*{#{extensions}}"]
|
283
|
+
|
284
|
+
(sidecar_files - [source_location] + sidecar_directory_files + nested_component_files).uniq
|
285
|
+
end
|
286
|
+
|
209
287
|
# Render a component collection.
|
210
288
|
def with_collection(collection, **args)
|
211
289
|
Collection.new(self, collection, **args)
|
@@ -239,7 +317,7 @@ module ViewComponent
|
|
239
317
|
end
|
240
318
|
|
241
319
|
def compiled?
|
242
|
-
|
320
|
+
compiler.compiled?
|
243
321
|
end
|
244
322
|
|
245
323
|
# Compile templates to instance methods, assuming they haven't been compiled already.
|
@@ -247,11 +325,11 @@ module ViewComponent
|
|
247
325
|
# Do as much work as possible in this step, as doing so reduces the amount
|
248
326
|
# of work done each time a component is rendered.
|
249
327
|
def compile(raise_errors: false)
|
250
|
-
|
328
|
+
compiler.compile(raise_errors: raise_errors)
|
251
329
|
end
|
252
330
|
|
253
|
-
def
|
254
|
-
@
|
331
|
+
def compiler
|
332
|
+
@_compiler ||= Compiler.new(self)
|
255
333
|
end
|
256
334
|
|
257
335
|
# we'll eventually want to update this to support other types
|
@@ -268,6 +346,11 @@ module ViewComponent
|
|
268
346
|
end
|
269
347
|
|
270
348
|
def with_content_areas(*areas)
|
349
|
+
ActiveSupport::Deprecation.warn(
|
350
|
+
"`with_content_areas` is deprecated and will be removed in ViewComponent v3.0.0.\n" \
|
351
|
+
"Use slots (https://viewcomponent.org/guide/slots.html) instead."
|
352
|
+
)
|
353
|
+
|
271
354
|
if areas.include?(:content)
|
272
355
|
raise ArgumentError.new ":content is a reserved content area name. Please use another name, such as ':body'"
|
273
356
|
end
|
@@ -319,7 +402,7 @@ module ViewComponent
|
|
319
402
|
def validate_initialization_parameters!
|
320
403
|
return unless initialize_parameter_names.include?(RESERVED_PARAMETER)
|
321
404
|
|
322
|
-
raise
|
405
|
+
raise ViewComponent::ComponentError.new(
|
323
406
|
"#{self} initializer cannot contain " \
|
324
407
|
"`#{RESERVED_PARAMETER}` since it will override a " \
|
325
408
|
"public ViewComponent method."
|
@@ -339,7 +422,7 @@ module ViewComponent
|
|
339
422
|
end
|
340
423
|
|
341
424
|
def counter_argument_present?
|
342
|
-
|
425
|
+
initialize_parameter_names.include?(collection_counter_parameter)
|
343
426
|
end
|
344
427
|
|
345
428
|
private
|
@@ -13,12 +13,18 @@ module ViewComponent
|
|
13
13
|
def compile(raise_errors: false)
|
14
14
|
return if compiled?
|
15
15
|
|
16
|
+
subclass_instance_methods = component_class.instance_methods(false)
|
17
|
+
|
18
|
+
if subclass_instance_methods.include?(:with_content) && raise_errors
|
19
|
+
raise ViewComponent::ComponentError.new("#{component_class} implements a reserved method, `with_content`.")
|
20
|
+
end
|
21
|
+
|
16
22
|
if template_errors.present?
|
17
23
|
raise ViewComponent::TemplateError.new(template_errors) if raise_errors
|
18
24
|
return false
|
19
25
|
end
|
20
26
|
|
21
|
-
if
|
27
|
+
if subclass_instance_methods.include?(:before_render_check)
|
22
28
|
ActiveSupport::Deprecation.warn(
|
23
29
|
"`before_render_check` will be removed in v3.0.0. Use `before_render` instead."
|
24
30
|
)
|
@@ -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
|
@@ -12,7 +12,7 @@ module ViewComponent
|
|
12
12
|
options = app.config.view_component
|
13
13
|
|
14
14
|
options.render_monkey_patch_enabled = true if options.render_monkey_patch_enabled.nil?
|
15
|
-
options.show_previews = Rails.env.development? if options.show_previews.nil?
|
15
|
+
options.show_previews = Rails.env.development? || Rails.env.test? if options.show_previews.nil?
|
16
16
|
options.preview_route ||= ViewComponent::Base.preview_route
|
17
17
|
options.preview_controller ||= ViewComponent::Base.preview_controller
|
18
18
|
|
@@ -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)
|
@@ -60,7 +60,7 @@ module ViewComponent
|
|
60
60
|
# helper method with the same name as the slot.
|
61
61
|
#
|
62
62
|
# <%= render_inline(MyComponent.new) do |component| %>
|
63
|
-
#
|
63
|
+
# <% component.header(classes: "Foo") do %>
|
64
64
|
# <p>Bar</p>
|
65
65
|
# <% end %>
|
66
66
|
# <% end %>
|
@@ -95,7 +95,7 @@ module ViewComponent
|
|
95
95
|
# helper method with the same name as the slot.
|
96
96
|
#
|
97
97
|
# <h1>
|
98
|
-
#
|
98
|
+
# <% items.each do |item| %>
|
99
99
|
# <%= item %>
|
100
100
|
# <% end %>
|
101
101
|
# </h1>
|
@@ -107,11 +107,11 @@ module ViewComponent
|
|
107
107
|
# called multiple times to append to the slot.
|
108
108
|
#
|
109
109
|
# <%= render_inline(MyComponent.new) do |component| %>
|
110
|
-
#
|
110
|
+
# <% component.item(name: "Foo") do %>
|
111
111
|
# <p>One</p>
|
112
112
|
# <% end %>
|
113
113
|
#
|
114
|
-
#
|
114
|
+
# <% component.item(name: "Bar") do %>
|
115
115
|
# <p>two</p>
|
116
116
|
# <% end %>
|
117
117
|
# <% end %>
|
@@ -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")
|
@@ -227,7 +231,9 @@ module ViewComponent
|
|
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
233
|
renderable_value = if block_given?
|
230
|
-
slot_definition[:renderable_function].bind(self).call(*args, **kwargs)
|
234
|
+
slot_definition[:renderable_function].bind(self).call(*args, **kwargs) do |*args, **kwargs|
|
235
|
+
view_context.capture(*args, **kwargs, &block)
|
236
|
+
end
|
231
237
|
else
|
232
238
|
slot_definition[:renderable_function].bind(self).call(*args, **kwargs)
|
233
239
|
end
|
@@ -249,7 +255,7 @@ module ViewComponent
|
|
249
255
|
@_set_slots[slot_name] = slot
|
250
256
|
end
|
251
257
|
|
252
|
-
|
258
|
+
slot
|
253
259
|
end
|
254
260
|
end
|
255
261
|
end
|
@@ -39,7 +39,12 @@ module ViewComponent
|
|
39
39
|
end
|
40
40
|
|
41
41
|
def request
|
42
|
-
@request ||=
|
42
|
+
@request ||=
|
43
|
+
begin
|
44
|
+
request = ActionDispatch::TestRequest.create
|
45
|
+
request.session = ActionController::TestSession.new
|
46
|
+
request
|
47
|
+
end
|
43
48
|
end
|
44
49
|
|
45
50
|
def with_variant(variant)
|
@@ -60,6 +65,17 @@ module ViewComponent
|
|
60
65
|
@controller = old_controller
|
61
66
|
end
|
62
67
|
|
68
|
+
def with_request_url(path)
|
69
|
+
old_request_path_parameters = request.path_parameters
|
70
|
+
old_controller = defined?(@controller) && @controller
|
71
|
+
|
72
|
+
request.path_parameters = Rails.application.routes.recognize_path(path)
|
73
|
+
yield
|
74
|
+
ensure
|
75
|
+
request.path_parameters = old_request_path_parameters
|
76
|
+
@controller = old_controller
|
77
|
+
end
|
78
|
+
|
63
79
|
def build_controller(klass)
|
64
80
|
klass.new.tap { |c| c.request = request }.extend(Rails.application.routes.url_helpers)
|
65
81
|
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.32.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: 2021-
|
11
|
+
date: 2021-05-21 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
|
@@ -198,7 +212,21 @@ dependencies:
|
|
198
212
|
- - "~>"
|
199
213
|
- !ruby/object:Gem::Version
|
200
214
|
version: '0.13'
|
201
|
-
|
215
|
+
- !ruby/object:Gem::Dependency
|
216
|
+
name: yard
|
217
|
+
requirement: !ruby/object:Gem::Requirement
|
218
|
+
requirements:
|
219
|
+
- - "~>"
|
220
|
+
- !ruby/object:Gem::Version
|
221
|
+
version: 0.9.25
|
222
|
+
type: :development
|
223
|
+
prerelease: false
|
224
|
+
version_requirements: !ruby/object:Gem::Requirement
|
225
|
+
requirements:
|
226
|
+
- - "~>"
|
227
|
+
- !ruby/object:Gem::Version
|
228
|
+
version: 0.9.25
|
229
|
+
description:
|
202
230
|
email:
|
203
231
|
- opensource+view_component@github.com
|
204
232
|
executables: []
|
@@ -232,6 +260,7 @@ files:
|
|
232
260
|
- lib/view_component/collection.rb
|
233
261
|
- lib/view_component/compile_cache.rb
|
234
262
|
- lib/view_component/compiler.rb
|
263
|
+
- lib/view_component/component_error.rb
|
235
264
|
- lib/view_component/engine.rb
|
236
265
|
- lib/view_component/preview.rb
|
237
266
|
- lib/view_component/preview_template_error.rb
|
@@ -249,13 +278,15 @@ files:
|
|
249
278
|
- lib/view_component/template_error.rb
|
250
279
|
- lib/view_component/test_case.rb
|
251
280
|
- lib/view_component/test_helpers.rb
|
281
|
+
- lib/view_component/translatable.rb
|
252
282
|
- lib/view_component/version.rb
|
283
|
+
- lib/view_component/with_content_helper.rb
|
253
284
|
homepage: https://github.com/github/view_component
|
254
285
|
licenses:
|
255
286
|
- MIT
|
256
287
|
metadata:
|
257
288
|
allowed_push_host: https://rubygems.org
|
258
|
-
post_install_message:
|
289
|
+
post_install_message:
|
259
290
|
rdoc_options: []
|
260
291
|
require_paths:
|
261
292
|
- lib
|
@@ -270,8 +301,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
270
301
|
- !ruby/object:Gem::Version
|
271
302
|
version: '0'
|
272
303
|
requirements: []
|
273
|
-
rubygems_version: 3.
|
274
|
-
signing_key:
|
304
|
+
rubygems_version: 3.2.3
|
305
|
+
signing_key:
|
275
306
|
specification_version: 4
|
276
307
|
summary: View components for Rails
|
277
308
|
test_files: []
|