view_component 4.0.0.alpha6 → 4.0.0.rc1
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.
- checksums.yaml +4 -4
- data/app/controllers/concerns/view_component/preview_actions.rb +3 -3
- data/app/views/test_mailer/test_url_email.html.erb +1 -0
- data/docs/CHANGELOG.md +113 -4
- data/lib/view_component/base.rb +54 -56
- data/lib/view_component/compiler.rb +13 -3
- data/lib/view_component/config.rb +3 -9
- data/lib/view_component/configurable.rb +1 -1
- data/lib/view_component/engine.rb +4 -4
- data/lib/view_component/errors.rb +0 -12
- data/lib/view_component/inline_template.rb +1 -1
- data/lib/view_component/slotable.rb +44 -43
- data/lib/view_component/system_spec_helpers.rb +11 -0
- data/lib/view_component/template.rb +4 -5
- data/lib/view_component/translatable.rb +7 -7
- data/lib/view_component/version.rb +1 -1
- data/lib/view_component.rb +8 -5
- metadata +4 -2
- data/lib/view_component/use_helpers.rb +0 -41
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a112fdf735637f91b77151d96244fac83f0d78141df7a2a0ff8f2d72d0df2d0
|
4
|
+
data.tar.gz: 39c33408d7f256dc0da92edb17ca18460d776e6f1be89ba63d28e3ea102f8ba9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b4c4f99335d6c2be623f1bd167e3d27567a7093acb388be1c245a6652423e4cc0ee8a16946da2d5ca9d35dd0283fecb1647888def129ec6fb75b82f427379558
|
7
|
+
data.tar.gz: 0ccb738fb45917ee49886ad2b1359a9bd3d98bf86d7e25f81422cffa5390514fe45d254a1e2d5900b6aa5649f428fa512d6a26c6d486d67546da8729dcc0d26f
|
@@ -8,7 +8,7 @@ module ViewComponent
|
|
8
8
|
prepend_view_path File.expand_path("../../../views", __dir__)
|
9
9
|
|
10
10
|
around_action :set_locale, only: :previews
|
11
|
-
before_action :require_local!, unless: :
|
11
|
+
before_action :require_local!, unless: :previews_enabled?
|
12
12
|
|
13
13
|
content_security_policy(false)
|
14
14
|
|
@@ -51,8 +51,8 @@ module ViewComponent
|
|
51
51
|
end
|
52
52
|
|
53
53
|
# :doc:
|
54
|
-
def
|
55
|
-
ViewComponent::Base.config.
|
54
|
+
def previews_enabled?
|
55
|
+
ViewComponent::Base.config.previews.enabled
|
56
56
|
end
|
57
57
|
|
58
58
|
# :doc:
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= render(UrlForMailerComponent.new) %>
|
data/docs/CHANGELOG.md
CHANGED
@@ -10,6 +10,111 @@ nav_order: 6
|
|
10
10
|
|
11
11
|
## main
|
12
12
|
|
13
|
+
## 4.0.0.rc1
|
14
|
+
|
15
|
+
Almost six years after releasing [v1.0.0](https://github.com/ViewComponent/view_component/releases/tag/v1.0.0), we're proud to ship the first release candidate of ViewComponent 4. This release marks a shift towards a Long Term Support model for the project, having reached significant feature maturity. While contributions are always welcome, we're unlikely to accept further breaking changes or major feature additions.
|
16
|
+
|
17
|
+
Please report any issues at [https://github.com/ViewComponent/view_component/issues](https://github.com/ViewComponent/view_component/issues).
|
18
|
+
|
19
|
+
### Breaking changes (production)
|
20
|
+
|
21
|
+
* Remove dependency on `ActionView::Base`, eliminating the need for capture compatibility patch. In some edge cases, this change may require switching to use the `helpers.` proxy.
|
22
|
+
* Require [non-EOL](https://endoflife.date/rails) Rails (`>= 7.1.0`) and Ruby (`>= 3.2.0`).
|
23
|
+
* Remove `render_component` and `render` monkey patch configured with `render_monkey_patch_enabled`.
|
24
|
+
* Remove deprecated `use_helper(s)`. Use `include MyHelper` or `helpers.` proxy instead.
|
25
|
+
* Support compatibility with `Dry::Initializer`. As a result, `EmptyOrInvalidInitializerError` will no longer be raised.
|
26
|
+
* Remove default initializer from `ViewComponent::Base`. Previously, `ViewComponent::Base` defined a catch-all initializer that allowed components without an initializer defined to be passed arbitrary arguments.
|
27
|
+
* Remove `use_deprecated_instrumentation_name` configuration option. Events will always use `render.view_component` name.
|
28
|
+
* Remove unnecessary `#format` methods that returned `nil`.
|
29
|
+
* Remove support for variant names containing `.` to be consistent with Rails.
|
30
|
+
* Rename internal methods to have `__vc_` prefix if they shouldn't be used by consumers. Make internal constants private. Make `Collection#components`, `Slotable#register_polymorphic_slot` private. Remove unused `ComponentError` class.
|
31
|
+
* Use ActionView's `lookup_context` for picking templates instead of the request format.
|
32
|
+
|
33
|
+
3.15 added support for using templates that match the request format, that is if `/resource.csv` is requested then
|
34
|
+
ViewComponents would pick `_component.csv.erb` over `_component.html.erb`.
|
35
|
+
|
36
|
+
With this release, the request format is no longer considered and instead ViewComponent will use the Rails logic for picking the most appropriate template type, that is the csv template will be used if it matches the `Accept` header or because the controller uses a `respond_to` block to pick the response format.
|
37
|
+
|
38
|
+
### Breaking changes (dev/test)
|
39
|
+
|
40
|
+
* Rename `config.generate.component_parent_class` to `config.generate.parent_class`.
|
41
|
+
* Remove `config.test_controller` in favor of `vc_test_controller_class` test helper method.
|
42
|
+
* `config.component_parent_class` is now `config.generate.component_parent_class`, moving the generator-specific option to the generator configuration namespace.
|
43
|
+
* Move previews-related configuration (`enabled`, `route`, `paths`, `default_layout`, `controller`) to under `previews` namespace.
|
44
|
+
* `config.view_component_path` is now `config.generate.path`, as components have long since been able to exist in any directory.
|
45
|
+
* `--inline` generator option now generates inline template. Use `--call` to generate `#call` method.
|
46
|
+
* Remove broken integration with `rails stats` that ignored components outside of `app/components`.
|
47
|
+
* Remove `preview_source` functionality. Consider using [Lookbook](https://lookbook.build/) instead.
|
48
|
+
* Use `Nokogiri::HTML5` instead of `Nokogiri::HTML4` for test helpers.
|
49
|
+
* Move generators to a ViewComponent namespace.
|
50
|
+
|
51
|
+
Before, ViewComponent generators pollute the generator namespace with a bunch of top level items, and claim the generic "component" name.
|
52
|
+
|
53
|
+
Now, generators live in a "view_component" module/namespace, so what was before `rails g
|
54
|
+
component` is now `rails g view_component:component`.
|
55
|
+
|
56
|
+
### New features
|
57
|
+
|
58
|
+
* Add `SystemSpecHelpers` for use with RSpec.
|
59
|
+
* Add support for including `Turbo::StreamsHelper`.
|
60
|
+
* Add template annotations for components with `def call`.
|
61
|
+
* Graduate `SlotableDefault` to be included by default.
|
62
|
+
* Add `#current_template` accessor and `Template#path` for diagnostic usage.
|
63
|
+
* Reduce string allocations during compilation.
|
64
|
+
|
65
|
+
### Bug fixes
|
66
|
+
|
67
|
+
* Fix bug where virtual path wasn't reset, breaking translations outside of components.
|
68
|
+
* Fix bug where `config.previews.enabled` didn't function properly in production environments.
|
69
|
+
* Fix bug where response format wasn't set, which caused issues with Turbo Frames.
|
70
|
+
* Fix bug in `SlotableDefault` where default couldn't be overridden when content was passed as a block.
|
71
|
+
* Fix bug where request-aware helpers didn't work outside of the request context.
|
72
|
+
* `ViewComponentsSystemTestController` shouldn't be useable outside of test environment
|
73
|
+
|
74
|
+
### Non-functional changes
|
75
|
+
|
76
|
+
* Remove unnecessary usage of `ruby2_keywords`.
|
77
|
+
* Remove unnecessary `respond_to` checks.
|
78
|
+
* Require MFA when publishing to RubyGems.
|
79
|
+
* Clean up project dependencies, relaxing versions of development gems.
|
80
|
+
* Add test case for absolute URL path helpers in mailers.
|
81
|
+
* Update documentation on performance to reflect more representative benchmark showing 2-3x speed increase over partials.
|
82
|
+
* Add documentation note about instrumentation negatively affecting performance.
|
83
|
+
* Remove unnecessary ENABLE_RELOADING test suite flag.
|
84
|
+
* `config.previews.default_layout` should default to nil.
|
85
|
+
* Add test coverage for uncovered code.
|
86
|
+
* Test against `turbo-rails` `v2` and `rspec-rails` `v7`.
|
87
|
+
|
88
|
+
## 4.0.0.alpha7
|
89
|
+
|
90
|
+
* BREAKING: Remove deprecated `use_helper(s)`. Use `include MyHelper` or `helpers.` proxy instead.
|
91
|
+
|
92
|
+
*Joel Hawksley*
|
93
|
+
|
94
|
+
* BREAKING: Support compatibility with `Dry::Initializer`. As a result, `EmptyOrInvalidInitializerError` will no longer be raised.
|
95
|
+
|
96
|
+
*Joel Hawksley*
|
97
|
+
|
98
|
+
* BREAKING: Rename `config.generate.component_parent_class` to `config.generate.parent_class`.
|
99
|
+
|
100
|
+
*Joel Hawksley*
|
101
|
+
|
102
|
+
* Fix bug where `config.previews.enabled` didn't function properly in production environments.
|
103
|
+
|
104
|
+
*Joel Hawksley*
|
105
|
+
|
106
|
+
* `config.previews.default_layout` should default to nil.
|
107
|
+
|
108
|
+
*Joel Hawksley*
|
109
|
+
|
110
|
+
* Add test case for absolute URL path helpers in mailers.
|
111
|
+
|
112
|
+
*Joel Hawksley*
|
113
|
+
|
114
|
+
* Fix bug where response format wasn't set, which caused issues with Turbo Frames.
|
115
|
+
|
116
|
+
*Joel Hawksley*
|
117
|
+
|
13
118
|
## 4.0.0.alpha6
|
14
119
|
|
15
120
|
* BREAKING: Remove `config.test_controller` in favor of `vc_test_controller_class` test helper method.
|
@@ -129,11 +234,11 @@ This release makes the following breaking changes:
|
|
129
234
|
|
130
235
|
* BREAKING: Use ActionView's `lookup_context` for picking templates instead of the request format.
|
131
236
|
|
132
|
-
3.15 added support for using templates that match the request format,
|
237
|
+
3.15 added support for using templates that match the request format, that is if `/resource.csv` is requested then
|
133
238
|
ViewComponents would pick `_component.csv.erb` over `_component.html.erb`.
|
134
239
|
|
135
240
|
With this release, the request format is no longer considered and instead ViewComponent will use the Rails logic
|
136
|
-
for picking the most appropriate template type,
|
241
|
+
for picking the most appropriate template type, that is the csv template will be used if it matches the `Accept` header
|
137
242
|
or because the controller uses a `respond_to` block to pick the response format.
|
138
243
|
|
139
244
|
*Stephen Nelson*
|
@@ -142,11 +247,11 @@ This release makes the following breaking changes:
|
|
142
247
|
|
143
248
|
*Joel Hawksley*
|
144
249
|
|
145
|
-
* Fix bug where request-aware helpers
|
250
|
+
* Fix bug where request-aware helpers didn't work outside of the request context.
|
146
251
|
|
147
252
|
*Joel Hawksley*, *Stephen Nelson*
|
148
253
|
|
149
|
-
* `ViewComponentsSystemTestController`
|
254
|
+
* `ViewComponentsSystemTestController` shouldn't be useable outside of test environment
|
150
255
|
|
151
256
|
*Joel Hawksley*, *Stephen Nelson*
|
152
257
|
|
@@ -186,6 +291,10 @@ This release makes the following breaking changes:
|
|
186
291
|
|
187
292
|
*Simon Fish*
|
188
293
|
|
294
|
+
* Deprecate `use_helper(s)`. Use `include MyHelper` or `helpers.` proxy instead.
|
295
|
+
|
296
|
+
*Joel Hawksley*
|
297
|
+
|
189
298
|
* Reduce string allocations during compilation.
|
190
299
|
|
191
300
|
*Jonathan del Strother*
|
data/lib/view_component/base.rb
CHANGED
@@ -14,7 +14,6 @@ require "view_component/slotable"
|
|
14
14
|
require "view_component/template"
|
15
15
|
require "view_component/translatable"
|
16
16
|
require "view_component/with_content_helper"
|
17
|
-
require "view_component/use_helpers"
|
18
17
|
|
19
18
|
module ActionView
|
20
19
|
class OutputBuffer
|
@@ -48,11 +47,11 @@ module ViewComponent
|
|
48
47
|
end
|
49
48
|
|
50
49
|
include ActionView::Helpers
|
50
|
+
include Rails.application.routes.url_helpers if defined?(Rails) && Rails.application
|
51
51
|
include ERB::Escape
|
52
52
|
include ActiveSupport::CoreExt::ERBUtil
|
53
53
|
|
54
54
|
include ViewComponent::InlineTemplate
|
55
|
-
include ViewComponent::UseHelpers
|
56
55
|
include ViewComponent::Slotable
|
57
56
|
include ViewComponent::Translatable
|
58
57
|
include ViewComponent::WithContentHelper
|
@@ -70,8 +69,9 @@ module ViewComponent
|
|
70
69
|
delegate :content_security_policy_nonce, to: :helpers
|
71
70
|
|
72
71
|
# Config option that strips trailing whitespace in templates before compiling them.
|
73
|
-
class_attribute :__vc_strip_trailing_whitespace, instance_accessor: false, instance_predicate: false
|
74
|
-
|
72
|
+
class_attribute :__vc_strip_trailing_whitespace, instance_accessor: false, instance_predicate: false, default: false
|
73
|
+
|
74
|
+
class_attribute :__vc_response_format, instance_accessor: false, instance_predicate: false, default: nil
|
75
75
|
|
76
76
|
attr_accessor :__vc_original_view_context
|
77
77
|
attr_reader :current_template
|
@@ -91,6 +91,11 @@ module ViewComponent
|
|
91
91
|
|
92
92
|
using RequestDetails
|
93
93
|
|
94
|
+
# Including `Rails.application.routes.url_helpers` defines an initializer that accepts (...),
|
95
|
+
# so we have to define our own empty initializer to overwrite it.
|
96
|
+
def initialize
|
97
|
+
end
|
98
|
+
|
94
99
|
# Entrypoint for rendering components.
|
95
100
|
#
|
96
101
|
# - `view_context`: ActionView context from calling view
|
@@ -103,6 +108,7 @@ module ViewComponent
|
|
103
108
|
self.class.__vc_compile(raise_errors: true)
|
104
109
|
|
105
110
|
@view_context = view_context
|
111
|
+
old_virtual_path = view_context.instance_variable_get(:@virtual_path)
|
106
112
|
self.__vc_original_view_context ||= view_context
|
107
113
|
|
108
114
|
@output_buffer = view_context.output_buffer
|
@@ -135,13 +141,15 @@ module ViewComponent
|
|
135
141
|
value = nil
|
136
142
|
|
137
143
|
@output_buffer.with_buffer do
|
144
|
+
@view_context.instance_variable_set(:@virtual_path, virtual_path)
|
145
|
+
|
138
146
|
rendered_template = render_template_for(@__vc_requested_details).to_s
|
139
147
|
|
140
148
|
# Avoid allocating new string when output_preamble and output_postamble are blank
|
141
149
|
value = if output_preamble.blank? && output_postamble.blank?
|
142
150
|
rendered_template
|
143
151
|
else
|
144
|
-
|
152
|
+
__vc_safe_output_preamble + rendered_template + __vc_safe_output_postamble
|
145
153
|
end
|
146
154
|
end
|
147
155
|
|
@@ -155,6 +163,7 @@ module ViewComponent
|
|
155
163
|
""
|
156
164
|
end
|
157
165
|
ensure
|
166
|
+
view_context.instance_variable_set(:@virtual_path, old_virtual_path)
|
158
167
|
@current_template = old_current_template
|
159
168
|
end
|
160
169
|
|
@@ -334,6 +343,10 @@ module ViewComponent
|
|
334
343
|
__vc_render_in_block_provided? || __vc_content_set_by_with_content_defined?
|
335
344
|
end
|
336
345
|
|
346
|
+
def format
|
347
|
+
self.class.__vc_response_format
|
348
|
+
end
|
349
|
+
|
337
350
|
private
|
338
351
|
|
339
352
|
attr_reader :view_context
|
@@ -346,11 +359,7 @@ module ViewComponent
|
|
346
359
|
defined?(@__vc_content_set_by_with_content)
|
347
360
|
end
|
348
361
|
|
349
|
-
def
|
350
|
-
defined?(@__vc_content_evaluated) && @__vc_content_evaluated
|
351
|
-
end
|
352
|
-
|
353
|
-
def maybe_escape_html(text)
|
362
|
+
def __vc_maybe_escape_html(text)
|
354
363
|
return text if @current_template && !@current_template.html?
|
355
364
|
return text if text.blank?
|
356
365
|
|
@@ -362,14 +371,14 @@ module ViewComponent
|
|
362
371
|
end
|
363
372
|
end
|
364
373
|
|
365
|
-
def
|
366
|
-
|
374
|
+
def __vc_safe_output_preamble
|
375
|
+
__vc_maybe_escape_html(output_preamble) do
|
367
376
|
Kernel.warn("WARNING: The #{self.class} component was provided an HTML-unsafe preamble. The preamble will be automatically escaped, but you may want to investigate.")
|
368
377
|
end
|
369
378
|
end
|
370
379
|
|
371
|
-
def
|
372
|
-
|
380
|
+
def __vc_safe_output_postamble
|
381
|
+
__vc_maybe_escape_html(output_postamble) do
|
373
382
|
Kernel.warn("WARNING: The #{self.class} component was provided an HTML-unsafe postamble. The postamble will be automatically escaped, but you may want to investigate.")
|
374
383
|
end
|
375
384
|
end
|
@@ -432,12 +441,12 @@ module ViewComponent
|
|
432
441
|
#
|
433
442
|
# Defaults to `false`.
|
434
443
|
#
|
435
|
-
# ####
|
444
|
+
# #### ßparent_class
|
436
445
|
#
|
437
446
|
# Parent class for generated components
|
438
447
|
#
|
439
448
|
# ```ruby
|
440
|
-
# config.view_component.generate.
|
449
|
+
# config.view_component.generate.parent_class = "MyBaseComponent"
|
441
450
|
# ```
|
442
451
|
#
|
443
452
|
# Defaults to nil. If this is falsy, generators will use
|
@@ -511,6 +520,11 @@ module ViewComponent
|
|
511
520
|
Collection.new(self, collection, spacer_component, **args)
|
512
521
|
end
|
513
522
|
|
523
|
+
# @private
|
524
|
+
def __vc_compile(raise_errors: false, force: false)
|
525
|
+
__vc_compiler.compile(raise_errors: raise_errors, force: force)
|
526
|
+
end
|
527
|
+
|
514
528
|
# @private
|
515
529
|
def inherited(child)
|
516
530
|
# Compile so child will inherit compiled `call_*` template methods that
|
@@ -533,12 +547,6 @@ module ViewComponent
|
|
533
547
|
RUBY
|
534
548
|
end
|
535
549
|
|
536
|
-
# If Rails application is loaded, add application url_helpers to the component context
|
537
|
-
# we need to check this to use this gem as a dependency
|
538
|
-
if defined?(Rails) && Rails.application && !(child < Rails.application.routes.url_helpers)
|
539
|
-
child.include Rails.application.routes.url_helpers
|
540
|
-
end
|
541
|
-
|
542
550
|
# Derive the source location of the component Ruby file from the call stack.
|
543
551
|
# We need to ignore `inherited` frames here as they indicate that `inherited`
|
544
552
|
# has been re-defined by the consuming application, likely in ApplicationComponent.
|
@@ -548,7 +556,7 @@ module ViewComponent
|
|
548
556
|
child.virtual_path = child.name&.underscore
|
549
557
|
|
550
558
|
# Set collection parameter to the extended component
|
551
|
-
child.with_collection_parameter
|
559
|
+
child.with_collection_parameter(__vc_provided_collection_parameter)
|
552
560
|
|
553
561
|
if instance_methods(false).include?(:render_template_for)
|
554
562
|
vc_ancestor_calls = defined?(@__vc_ancestor_calls) ? @__vc_ancestor_calls.dup : []
|
@@ -570,11 +578,6 @@ module ViewComponent
|
|
570
578
|
__vc_compile unless __vc_compiled?
|
571
579
|
end
|
572
580
|
|
573
|
-
# @private
|
574
|
-
def __vc_compile(raise_errors: false, force: false)
|
575
|
-
__vc_compiler.compile(raise_errors: raise_errors, force: force)
|
576
|
-
end
|
577
|
-
|
578
581
|
# @private
|
579
582
|
def __vc_compiler
|
580
583
|
@__vc_compiler ||= Compiler.new(self)
|
@@ -588,8 +591,8 @@ module ViewComponent
|
|
588
591
|
#
|
589
592
|
# @param parameter [Symbol] The parameter name used when rendering elements of a collection.
|
590
593
|
def with_collection_parameter(parameter)
|
591
|
-
@
|
592
|
-
@
|
594
|
+
@__vc_provided_collection_parameter = parameter
|
595
|
+
@__vc_initialize_parameters = nil
|
593
596
|
end
|
594
597
|
|
595
598
|
# Strips trailing whitespace from templates before compiling them.
|
@@ -619,17 +622,10 @@ module ViewComponent
|
|
619
622
|
# rendering is optional.
|
620
623
|
# @private
|
621
624
|
def __vc_validate_collection_parameter!(validate_default: false)
|
622
|
-
parameter = validate_default ? __vc_collection_parameter :
|
625
|
+
parameter = validate_default ? __vc_collection_parameter : __vc_provided_collection_parameter
|
623
626
|
|
624
627
|
return unless parameter
|
625
|
-
return if
|
626
|
-
|
627
|
-
# If Ruby can't parse the component class, then the initialize
|
628
|
-
# parameters will be empty and ViewComponent will not be able to render
|
629
|
-
# the component.
|
630
|
-
if initialize_parameters.empty?
|
631
|
-
raise EmptyOrInvalidInitializerError.new(name, parameter)
|
632
|
-
end
|
628
|
+
return if __vc_initialize_parameter_names.include?(parameter) || __vc_splatted_keyword_argument_present?
|
633
629
|
|
634
630
|
raise MissingCollectionArgumentError.new(name, parameter)
|
635
631
|
end
|
@@ -639,55 +635,57 @@ module ViewComponent
|
|
639
635
|
# methods.
|
640
636
|
# @private
|
641
637
|
def __vc_validate_initialization_parameters!
|
642
|
-
return unless
|
638
|
+
return unless __vc_initialize_parameter_names.include?(:content)
|
643
639
|
|
644
640
|
raise ReservedParameterError.new(name, :content)
|
645
641
|
end
|
646
642
|
|
647
643
|
# @private
|
648
644
|
def __vc_collection_parameter
|
649
|
-
|
645
|
+
@__vc_provided_collection_parameter ||= name && name.demodulize.underscore.chomp("_component").to_sym
|
650
646
|
end
|
651
647
|
|
652
648
|
# @private
|
653
649
|
def __vc_collection_counter_parameter
|
654
|
-
:"#{__vc_collection_parameter}_counter"
|
650
|
+
@__vc_collection_counter_parameter ||= :"#{__vc_collection_parameter}_counter"
|
655
651
|
end
|
656
652
|
|
657
653
|
# @private
|
658
654
|
def __vc_counter_argument_present?
|
659
|
-
|
655
|
+
__vc_initialize_parameter_names.include?(__vc_collection_counter_parameter)
|
660
656
|
end
|
661
657
|
|
662
658
|
# @private
|
663
659
|
def __vc_collection_iteration_parameter
|
664
|
-
:"#{__vc_collection_parameter}_iteration"
|
660
|
+
@__vc_collection_iteration_parameter ||= :"#{__vc_collection_parameter}_iteration"
|
665
661
|
end
|
666
662
|
|
667
663
|
# @private
|
668
664
|
def __vc_iteration_argument_present?
|
669
|
-
|
665
|
+
__vc_initialize_parameter_names.include?(__vc_collection_iteration_parameter)
|
670
666
|
end
|
671
667
|
|
672
668
|
private
|
673
669
|
|
674
|
-
def
|
675
|
-
|
676
|
-
!initialize_parameters.include?([:keyrest, :**]) # Un-named splatted keyword args don't count!
|
670
|
+
def __vc_splatted_keyword_argument_present?
|
671
|
+
__vc_initialize_parameters.flatten.include?(:keyrest)
|
677
672
|
end
|
678
673
|
|
679
|
-
def
|
680
|
-
|
681
|
-
|
682
|
-
|
674
|
+
def __vc_initialize_parameter_names
|
675
|
+
@__vc_initialize_parameter_names ||=
|
676
|
+
if respond_to?(:attribute_names)
|
677
|
+
attribute_names.map(&:to_sym)
|
678
|
+
else
|
679
|
+
__vc_initialize_parameters.map(&:last)
|
680
|
+
end
|
683
681
|
end
|
684
682
|
|
685
|
-
def
|
686
|
-
@
|
683
|
+
def __vc_initialize_parameters
|
684
|
+
@__vc_initialize_parameters ||= instance_method(:initialize).parameters
|
687
685
|
end
|
688
686
|
|
689
|
-
def
|
690
|
-
@
|
687
|
+
def __vc_provided_collection_parameter
|
688
|
+
@__vc_provided_collection_parameter ||= nil
|
691
689
|
end
|
692
690
|
end
|
693
691
|
|
@@ -48,7 +48,16 @@ module ViewComponent
|
|
48
48
|
|
49
49
|
define_render_template_for
|
50
50
|
|
51
|
-
|
51
|
+
# Set the format if the component only responds to a single format.
|
52
|
+
# Unfortunately we cannot determine which format a multi-format
|
53
|
+
# component will respond to until render time, so those components
|
54
|
+
# will not set the response format.
|
55
|
+
#
|
56
|
+
# TODO: Investigate upstream changes necessary to support multi-format renderables
|
57
|
+
unique_formats = templates.map(&:format).uniq
|
58
|
+
@component.__vc_response_format = unique_formats.last if unique_formats.one?
|
59
|
+
|
60
|
+
@component.__vc_register_default_slots
|
52
61
|
@component.__vc_build_i18n_backend
|
53
62
|
|
54
63
|
CompileCache.register(@component)
|
@@ -109,6 +118,7 @@ module ViewComponent
|
|
109
118
|
errors << "Couldn't find a template file or inline render method for #{@component}." if @templates.empty?
|
110
119
|
|
111
120
|
@templates
|
121
|
+
.reject { |template| template.inline_call? && !template.defined_on_self? }
|
112
122
|
.map { |template| [template.variant, template.format] }
|
113
123
|
.tally
|
114
124
|
.select { |_, count| count > 1 }
|
@@ -167,10 +177,10 @@ module ViewComponent
|
|
167
177
|
|
168
178
|
def gather_templates
|
169
179
|
@templates ||=
|
170
|
-
if @component.
|
180
|
+
if @component.__vc_inline_template.present?
|
171
181
|
[Template::Inline.new(
|
172
182
|
component: @component,
|
173
|
-
inline_template: @component.
|
183
|
+
inline_template: @component.__vc_inline_template
|
174
184
|
)]
|
175
185
|
else
|
176
186
|
path_parser = ActionView::Resolver::PathParser.new
|
@@ -118,9 +118,9 @@ module ViewComponent
|
|
118
118
|
#
|
119
119
|
# #### `#default_layout`
|
120
120
|
#
|
121
|
-
# A custom default layout used for the previews index page and individual previews. Defaults to `
|
121
|
+
# A custom default layout used for the previews index page and individual previews. Defaults to `nil`:
|
122
122
|
#
|
123
|
-
# config.view_component.previews.default_layout =
|
123
|
+
# config.view_component.previews.default_layout = "preview_layout"
|
124
124
|
#
|
125
125
|
|
126
126
|
# @!attribute instrumentation_enabled
|
@@ -128,12 +128,6 @@ module ViewComponent
|
|
128
128
|
# Whether ActiveSupport notifications are enabled.
|
129
129
|
# Defaults to `false`.
|
130
130
|
|
131
|
-
# @!attribute test_controller
|
132
|
-
# @return [String]
|
133
|
-
# The controller used for testing components.
|
134
|
-
# Can also be configured on a per-test basis using `#with_controller_class`.
|
135
|
-
# Defaults to `ApplicationController`.
|
136
|
-
|
137
131
|
def default_preview_paths
|
138
132
|
(default_rails_preview_paths + default_rails_engines_preview_paths).uniq
|
139
133
|
end
|
@@ -170,7 +164,7 @@ module ViewComponent
|
|
170
164
|
options.controller = "ViewComponentsController"
|
171
165
|
options.route = "/rails/view_components"
|
172
166
|
options.enabled = Rails.env.development? || Rails.env.test?
|
173
|
-
options.default_layout =
|
167
|
+
options.default_layout = nil
|
174
168
|
options.paths = default_preview_paths
|
175
169
|
options
|
176
170
|
end
|
@@ -5,7 +5,7 @@ module ViewComponent
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
included do
|
8
|
-
next if respond_to?(:config) && config.respond_to?(:view_component) && config.respond_to_missing?(:
|
8
|
+
next if respond_to?(:config) && config.respond_to?(:view_component) && config.respond_to_missing?(:instrumentation_enabled)
|
9
9
|
|
10
10
|
include ActiveSupport::Configurable
|
11
11
|
|
@@ -15,9 +15,9 @@ module ViewComponent
|
|
15
15
|
options[config_option] ||= ViewComponent::Base.public_send(config_option)
|
16
16
|
end
|
17
17
|
options.instrumentation_enabled = false if options.instrumentation_enabled.nil?
|
18
|
-
options.
|
18
|
+
options.previews.enabled = (Rails.env.development? || Rails.env.test?) if options.previews.enabled.nil?
|
19
19
|
|
20
|
-
if options.
|
20
|
+
if options.previews.enabled
|
21
21
|
# This is still necessary because when `config.view_component` is declared, `Rails.root` is unspecified.
|
22
22
|
options.previews.paths << "#{Rails.root}/test/components/previews" if defined?(Rails.root) && Dir.exist?(
|
23
23
|
"#{Rails.root}/test/components/previews"
|
@@ -36,7 +36,7 @@ module ViewComponent
|
|
36
36
|
initializer "view_component.set_autoload_paths" do |app|
|
37
37
|
options = app.config.view_component
|
38
38
|
|
39
|
-
if options.
|
39
|
+
if options.previews.enabled && !options.previews.paths.empty?
|
40
40
|
paths_to_add = options.previews.paths - ActiveSupport::Dependencies.autoload_paths
|
41
41
|
ActiveSupport::Dependencies.autoload_paths.concat(paths_to_add) if paths_to_add.any?
|
42
42
|
end
|
@@ -88,7 +88,7 @@ module ViewComponent
|
|
88
88
|
config.after_initialize do |app|
|
89
89
|
options = app.config.view_component
|
90
90
|
|
91
|
-
if options.
|
91
|
+
if options.previews.enabled
|
92
92
|
app.routes.prepend do
|
93
93
|
preview_controller = options.previews.controller.sub(/Controller$/, "").underscore
|
94
94
|
|
@@ -65,18 +65,6 @@ module ViewComponent
|
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
-
class EmptyOrInvalidInitializerError < StandardError
|
69
|
-
MESSAGE =
|
70
|
-
"The COMPONENT initializer is empty or invalid. " \
|
71
|
-
"It must accept the parameter `PARAMETER` to render it as a collection.\n\n" \
|
72
|
-
"To fix this issue, update the initializer to accept `PARAMETER`.\n\n" \
|
73
|
-
"See [the collections docs](https://viewcomponent.org/guide/collections.html) for more information on rendering collections."
|
74
|
-
|
75
|
-
def initialize(klass_name, parameter)
|
76
|
-
super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("PARAMETER", parameter.to_s))
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
68
|
class MissingCollectionArgumentError < StandardError
|
81
69
|
MESSAGE =
|
82
70
|
"The initializer for COMPONENT doesn't accept the parameter `PARAMETER`, " \
|
@@ -73,25 +73,25 @@ module ViewComponent
|
|
73
73
|
#
|
74
74
|
# <%= render_inline(MyComponent.new.with_header_content("Foo")) %>
|
75
75
|
def renders_one(slot_name, callable = nil)
|
76
|
-
|
76
|
+
__vc_validate_singular_slot_name(slot_name)
|
77
77
|
|
78
78
|
if callable.is_a?(Hash) && callable.key?(:types)
|
79
|
-
|
79
|
+
__vc_register_polymorphic_slot(slot_name, callable[:types], collection: false)
|
80
80
|
else
|
81
|
-
|
81
|
+
__vc_validate_plural_slot_name(ActiveSupport::Inflector.pluralize(slot_name).to_sym)
|
82
82
|
|
83
83
|
setter_method_name = :"with_#{slot_name}"
|
84
84
|
|
85
85
|
define_method setter_method_name do |*args, **kwargs, &block|
|
86
|
-
|
86
|
+
__vc_set_slot(slot_name, nil, *args, **kwargs, &block)
|
87
87
|
end
|
88
88
|
|
89
89
|
self::GeneratedSlotMethods.define_method slot_name do
|
90
|
-
|
90
|
+
__vc_get_slot(slot_name)
|
91
91
|
end
|
92
92
|
|
93
93
|
self::GeneratedSlotMethods.define_method :"#{slot_name}?" do
|
94
|
-
|
94
|
+
__vc_get_slot(slot_name).present?
|
95
95
|
end
|
96
96
|
|
97
97
|
define_method :"with_#{slot_name}_content" do |content|
|
@@ -100,7 +100,7 @@ module ViewComponent
|
|
100
100
|
self
|
101
101
|
end
|
102
102
|
|
103
|
-
|
103
|
+
__vc_register_slot(slot_name, collection: false, callable: callable)
|
104
104
|
end
|
105
105
|
end
|
106
106
|
|
@@ -142,18 +142,18 @@ module ViewComponent
|
|
142
142
|
# <% end %>
|
143
143
|
# <% end %>
|
144
144
|
def renders_many(slot_name, callable = nil)
|
145
|
-
|
145
|
+
__vc_validate_plural_slot_name(slot_name)
|
146
146
|
|
147
147
|
if callable.is_a?(Hash) && callable.key?(:types)
|
148
|
-
|
148
|
+
__vc_register_polymorphic_slot(slot_name, callable[:types], collection: true)
|
149
149
|
else
|
150
150
|
singular_name = ActiveSupport::Inflector.singularize(slot_name)
|
151
|
-
|
151
|
+
__vc_validate_singular_slot_name(ActiveSupport::Inflector.singularize(slot_name).to_sym)
|
152
152
|
|
153
153
|
setter_method_name = :"with_#{singular_name}"
|
154
154
|
|
155
155
|
define_method setter_method_name do |*args, **kwargs, &block|
|
156
|
-
|
156
|
+
__vc_set_slot(slot_name, nil, *args, **kwargs, &block)
|
157
157
|
end
|
158
158
|
|
159
159
|
define_method :"with_#{singular_name}_content" do |content|
|
@@ -165,22 +165,22 @@ module ViewComponent
|
|
165
165
|
define_method :"with_#{slot_name}" do |collection_args = nil, &block|
|
166
166
|
collection_args.map do |args|
|
167
167
|
if args.respond_to?(:to_hash)
|
168
|
-
|
168
|
+
__vc_set_slot(slot_name, nil, **args, &block)
|
169
169
|
else
|
170
|
-
|
170
|
+
__vc_set_slot(slot_name, nil, *args, &block)
|
171
171
|
end
|
172
172
|
end
|
173
173
|
end
|
174
174
|
|
175
175
|
self::GeneratedSlotMethods.define_method slot_name do
|
176
|
-
|
176
|
+
__vc_get_slot(slot_name)
|
177
177
|
end
|
178
178
|
|
179
179
|
self::GeneratedSlotMethods.define_method :"#{slot_name}?" do
|
180
|
-
|
180
|
+
__vc_get_slot(slot_name).present?
|
181
181
|
end
|
182
182
|
|
183
|
-
|
183
|
+
__vc_register_slot(slot_name, collection: true, callable: callable)
|
184
184
|
end
|
185
185
|
end
|
186
186
|
|
@@ -211,8 +211,9 @@ module ViewComponent
|
|
211
211
|
super
|
212
212
|
end
|
213
213
|
|
214
|
+
# @private
|
214
215
|
# Called by the compiler, as instance methods are not defined when slots are first registered
|
215
|
-
def
|
216
|
+
def __vc_register_default_slots
|
216
217
|
registered_slots.each do |slot_name, config|
|
217
218
|
default_method_name = :"default_#{slot_name}"
|
218
219
|
config[:default_method] = instance_methods.find { |method_name| method_name == default_method_name }
|
@@ -223,17 +224,17 @@ module ViewComponent
|
|
223
224
|
|
224
225
|
private
|
225
226
|
|
226
|
-
def
|
227
|
-
registered_slots[slot_name] =
|
227
|
+
def __vc_register_slot(slot_name, **kwargs)
|
228
|
+
registered_slots[slot_name] = __vc_define_slot(slot_name, **kwargs)
|
228
229
|
end
|
229
230
|
|
230
|
-
def
|
231
|
+
def __vc_register_polymorphic_slot(slot_name, types, collection:)
|
231
232
|
self::GeneratedSlotMethods.define_method(slot_name) do
|
232
|
-
|
233
|
+
__vc_get_slot(slot_name)
|
233
234
|
end
|
234
235
|
|
235
236
|
self::GeneratedSlotMethods.define_method(:"#{slot_name}?") do
|
236
|
-
|
237
|
+
__vc_get_slot(slot_name).present?
|
237
238
|
end
|
238
239
|
|
239
240
|
renderable_hash = types.each_with_object({}) do |(poly_type, poly_attributes_or_callable), memo|
|
@@ -252,7 +253,7 @@ module ViewComponent
|
|
252
253
|
"#{slot_name}_#{poly_type}"
|
253
254
|
end
|
254
255
|
|
255
|
-
memo[poly_type] =
|
256
|
+
memo[poly_type] = __vc_define_slot(
|
256
257
|
poly_slot_name, collection: collection, callable: poly_callable
|
257
258
|
)
|
258
259
|
|
@@ -263,7 +264,7 @@ module ViewComponent
|
|
263
264
|
end
|
264
265
|
|
265
266
|
define_method(setter_method_name) do |*args, **kwargs, &block|
|
266
|
-
|
267
|
+
__vc_set_polymorphic_slot(slot_name, poly_type, *args, **kwargs, &block)
|
267
268
|
end
|
268
269
|
|
269
270
|
define_method :"with_#{poly_slot_name}_content" do |content|
|
@@ -279,7 +280,7 @@ module ViewComponent
|
|
279
280
|
}
|
280
281
|
end
|
281
282
|
|
282
|
-
def
|
283
|
+
def __vc_define_slot(slot_name, collection:, callable:)
|
283
284
|
slot = {collection: collection}
|
284
285
|
return slot unless callable
|
285
286
|
|
@@ -302,18 +303,18 @@ module ViewComponent
|
|
302
303
|
slot
|
303
304
|
end
|
304
305
|
|
305
|
-
def
|
306
|
+
def __vc_validate_plural_slot_name(slot_name)
|
306
307
|
if RESERVED_NAMES[:plural].include?(slot_name.to_sym)
|
307
308
|
raise ReservedPluralSlotNameError.new(name, slot_name)
|
308
309
|
end
|
309
310
|
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
311
|
+
__vc_raise_if_slot_name_uncountable(slot_name)
|
312
|
+
__vc_raise_if_slot_conflicts_with_call(slot_name)
|
313
|
+
__vc_raise_if_slot_ends_with_question_mark(slot_name)
|
314
|
+
__vc_raise_if_slot_registered(slot_name)
|
314
315
|
end
|
315
316
|
|
316
|
-
def
|
317
|
+
def __vc_validate_singular_slot_name(slot_name)
|
317
318
|
if slot_name.to_sym == :content
|
318
319
|
raise ContentSlotNameError.new(name)
|
319
320
|
end
|
@@ -322,28 +323,28 @@ module ViewComponent
|
|
322
323
|
raise ReservedSingularSlotNameError.new(name, slot_name)
|
323
324
|
end
|
324
325
|
|
325
|
-
|
326
|
-
|
327
|
-
|
326
|
+
__vc_raise_if_slot_conflicts_with_call(slot_name)
|
327
|
+
__vc_raise_if_slot_ends_with_question_mark(slot_name)
|
328
|
+
__vc_raise_if_slot_registered(slot_name)
|
328
329
|
end
|
329
330
|
|
330
|
-
def
|
331
|
+
def __vc_raise_if_slot_registered(slot_name)
|
331
332
|
if registered_slots.key?(slot_name)
|
332
333
|
raise RedefinedSlotError.new(name, slot_name)
|
333
334
|
end
|
334
335
|
end
|
335
336
|
|
336
|
-
def
|
337
|
+
def __vc_raise_if_slot_ends_with_question_mark(slot_name)
|
337
338
|
raise SlotPredicateNameError.new(name, slot_name) if slot_name.to_s.end_with?("?")
|
338
339
|
end
|
339
340
|
|
340
|
-
def
|
341
|
+
def __vc_raise_if_slot_conflicts_with_call(slot_name)
|
341
342
|
if slot_name.start_with?("call_")
|
342
343
|
raise InvalidSlotNameError, "Slot cannot start with 'call_'. Please rename #{slot_name}"
|
343
344
|
end
|
344
345
|
end
|
345
346
|
|
346
|
-
def
|
347
|
+
def __vc_raise_if_slot_name_uncountable(slot_name)
|
347
348
|
slot_name = slot_name.to_s
|
348
349
|
if slot_name.pluralize == slot_name.singularize
|
349
350
|
raise UncountableSlotNameError.new(name, slot_name)
|
@@ -351,9 +352,9 @@ module ViewComponent
|
|
351
352
|
end
|
352
353
|
end
|
353
354
|
|
354
|
-
def
|
355
|
+
def __vc_get_slot(slot_name)
|
355
356
|
@__vc_set_slots ||= {}
|
356
|
-
content unless
|
357
|
+
content unless defined?(@__vc_content_evaluated) && @__vc_content_evaluated # ensure content is loaded so slots will be defined
|
357
358
|
|
358
359
|
# If the slot is set, return it
|
359
360
|
return @__vc_set_slots[slot_name] if @__vc_set_slots[slot_name]
|
@@ -376,7 +377,7 @@ module ViewComponent
|
|
376
377
|
end
|
377
378
|
end
|
378
379
|
|
379
|
-
def
|
380
|
+
def __vc_set_slot(slot_name, slot_definition = nil, *args, **kwargs, &block)
|
380
381
|
slot_definition ||= self.class.registered_slots[slot_name]
|
381
382
|
slot = Slot.new(self)
|
382
383
|
|
@@ -433,7 +434,7 @@ module ViewComponent
|
|
433
434
|
slot
|
434
435
|
end
|
435
436
|
|
436
|
-
def
|
437
|
+
def __vc_set_polymorphic_slot(slot_name, poly_type = nil, *args, **kwargs, &block)
|
437
438
|
slot_definition = self.class.registered_slots[slot_name]
|
438
439
|
|
439
440
|
if !slot_definition[:collection] && defined?(@__vc_set_slots) && @__vc_set_slots[slot_name]
|
@@ -442,7 +443,7 @@ module ViewComponent
|
|
442
443
|
|
443
444
|
poly_def = slot_definition[:renderable_hash][poly_type]
|
444
445
|
|
445
|
-
|
446
|
+
__vc_set_slot(slot_name, poly_def, *args, **kwargs, &block)
|
446
447
|
end
|
447
448
|
end
|
448
449
|
end
|
@@ -43,7 +43,7 @@ module ViewComponent
|
|
43
43
|
attr_reader :source
|
44
44
|
|
45
45
|
def initialize(component:, inline_template:)
|
46
|
-
details = ActionView::TemplateDetails.new(nil, inline_template.language.to_sym,
|
46
|
+
details = ActionView::TemplateDetails.new(nil, inline_template.language.to_sym, DEFAULT_FORMAT, nil)
|
47
47
|
|
48
48
|
super(
|
49
49
|
component: component,
|
@@ -63,7 +63,7 @@ module ViewComponent
|
|
63
63
|
class InlineCall < Template
|
64
64
|
def initialize(component:, method_name:, defined_on_self:)
|
65
65
|
variant = method_name.to_s.include?("call_") ? method_name.to_s.sub("call_", "").to_sym : nil
|
66
|
-
details = ActionView::TemplateDetails.new(nil, nil,
|
66
|
+
details = ActionView::TemplateDetails.new(nil, nil, DEFAULT_FORMAT, variant)
|
67
67
|
|
68
68
|
super(component: component, details: details)
|
69
69
|
|
@@ -82,7 +82,7 @@ module ViewComponent
|
|
82
82
|
def safe_method_name_call
|
83
83
|
m = safe_method_name
|
84
84
|
proc do
|
85
|
-
|
85
|
+
__vc_maybe_escape_html(send(m)) do
|
86
86
|
Kernel.warn("WARNING: The #{self.class} component rendered HTML-unsafe output. " \
|
87
87
|
"The output will be automatically escaped, but you may want to investigate.")
|
88
88
|
end
|
@@ -98,9 +98,8 @@ module ViewComponent
|
|
98
98
|
@component.silence_redefinition_of_method(call_method_name)
|
99
99
|
|
100
100
|
# rubocop:disable Style/EvalWithLocation
|
101
|
-
@component.class_eval <<~RUBY, @path, @lineno
|
101
|
+
@component.class_eval <<~RUBY, @path, @lineno
|
102
102
|
def #{call_method_name}
|
103
|
-
@view_context.instance_variable_set(:@virtual_path, virtual_path)
|
104
103
|
#{compiled_source}
|
105
104
|
end
|
106
105
|
RUBY
|
@@ -70,12 +70,12 @@ module ViewComponent
|
|
70
70
|
|
71
71
|
def initialize(scope:, load_paths:)
|
72
72
|
@__vc_i18n_scope = scope.split(".").map(&:to_sym)
|
73
|
-
@
|
73
|
+
@__vc_load_paths = load_paths
|
74
74
|
end
|
75
75
|
|
76
76
|
# Ensure the Simple backend won't load paths from ::I18n.load_path
|
77
77
|
def load_translations
|
78
|
-
super(@
|
78
|
+
super(@__vc_load_paths)
|
79
79
|
end
|
80
80
|
|
81
81
|
def scope_data(data)
|
@@ -100,7 +100,7 @@ module ViewComponent
|
|
100
100
|
key = self.class.__vc_i18n_key(key, options.delete(:scope))
|
101
101
|
as_html = HTML_SAFE_TRANSLATION_KEY.match?(key)
|
102
102
|
|
103
|
-
|
103
|
+
__vc_html_escape_translation_options!(options) if as_html
|
104
104
|
|
105
105
|
if key.start_with?(__vc_i18n_scope + ".")
|
106
106
|
translated =
|
@@ -113,7 +113,7 @@ module ViewComponent
|
|
113
113
|
return @view_context.translate(key, locale: locale, **options)
|
114
114
|
end
|
115
115
|
|
116
|
-
translated =
|
116
|
+
translated = __vc_html_safe_translation(translated) if as_html
|
117
117
|
translated
|
118
118
|
else
|
119
119
|
@view_context.translate(key, locale: locale, **options)
|
@@ -127,9 +127,9 @@ module ViewComponent
|
|
127
127
|
|
128
128
|
private
|
129
129
|
|
130
|
-
def
|
130
|
+
def __vc_html_safe_translation(translation)
|
131
131
|
if translation.respond_to?(:map)
|
132
|
-
translation.map { |element|
|
132
|
+
translation.map { |element| __vc_html_safe_translation(element) }
|
133
133
|
else
|
134
134
|
# It's assumed here that objects loaded by the i18n backend will respond to `#html_safe?`.
|
135
135
|
# It's reasonable that if we're in Rails, `active_support/core_ext/string/output_safety.rb`
|
@@ -138,7 +138,7 @@ module ViewComponent
|
|
138
138
|
end
|
139
139
|
end
|
140
140
|
|
141
|
-
def
|
141
|
+
def __vc_html_escape_translation_options!(options)
|
142
142
|
options.except(*::I18n::RESERVED_KEYS).each do |name, value|
|
143
143
|
next if name == :count && value.is_a?(Numeric)
|
144
144
|
|
data/lib/view_component.rb
CHANGED
@@ -7,7 +7,6 @@ module ViewComponent
|
|
7
7
|
extend ActiveSupport::Autoload
|
8
8
|
|
9
9
|
autoload :Base
|
10
|
-
autoload :CaptureCompatibility
|
11
10
|
autoload :Compiler
|
12
11
|
autoload :CompileCache
|
13
12
|
autoload :Config
|
@@ -15,11 +14,15 @@ module ViewComponent
|
|
15
14
|
autoload :InlineTemplate
|
16
15
|
autoload :Instrumentation
|
17
16
|
autoload :Preview
|
18
|
-
autoload :TestHelpers
|
19
|
-
autoload :SystemTestHelpers
|
20
|
-
autoload :TestCase
|
21
|
-
autoload :SystemTestCase
|
22
17
|
autoload :Translatable
|
18
|
+
|
19
|
+
if Rails.env.test?
|
20
|
+
autoload :TestHelpers
|
21
|
+
autoload :SystemSpecHelpers
|
22
|
+
autoload :SystemTestHelpers
|
23
|
+
autoload :TestCase
|
24
|
+
autoload :SystemTestCase
|
25
|
+
end
|
23
26
|
end
|
24
27
|
|
25
28
|
require "view_component/engine" if defined?(Rails::Engine)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: view_component
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.0.0.
|
4
|
+
version: 4.0.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ViewComponent Team
|
@@ -54,6 +54,7 @@ files:
|
|
54
54
|
- app/controllers/view_components_system_test_controller.rb
|
55
55
|
- app/views/test_mailer/test_asset_email.html.erb
|
56
56
|
- app/views/test_mailer/test_email.html.erb
|
57
|
+
- app/views/test_mailer/test_url_email.html.erb
|
57
58
|
- app/views/view_components/index.html.erb
|
58
59
|
- app/views/view_components/preview.html.erb
|
59
60
|
- app/views/view_components/previews.html.erb
|
@@ -74,13 +75,13 @@ files:
|
|
74
75
|
- lib/view_component/request_details.rb
|
75
76
|
- lib/view_component/slot.rb
|
76
77
|
- lib/view_component/slotable.rb
|
78
|
+
- lib/view_component/system_spec_helpers.rb
|
77
79
|
- lib/view_component/system_test_case.rb
|
78
80
|
- lib/view_component/system_test_helpers.rb
|
79
81
|
- lib/view_component/template.rb
|
80
82
|
- lib/view_component/test_case.rb
|
81
83
|
- lib/view_component/test_helpers.rb
|
82
84
|
- lib/view_component/translatable.rb
|
83
|
-
- lib/view_component/use_helpers.rb
|
84
85
|
- lib/view_component/version.rb
|
85
86
|
- lib/view_component/with_content_helper.rb
|
86
87
|
homepage: https://viewcomponent.org
|
@@ -88,6 +89,7 @@ licenses:
|
|
88
89
|
- MIT
|
89
90
|
metadata:
|
90
91
|
allowed_push_host: https://rubygems.org
|
92
|
+
rubygems_mfa_required: 'true'
|
91
93
|
source_code_uri: https://github.com/viewcomponent/view_component
|
92
94
|
changelog_uri: https://github.com/ViewComponent/view_component/blob/main/docs/CHANGELOG.md
|
93
95
|
rdoc_options: []
|
@@ -1,41 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ViewComponent::UseHelpers
|
4
|
-
extend ActiveSupport::Concern
|
5
|
-
|
6
|
-
class_methods do
|
7
|
-
def use_helpers(*args, from: nil, prefix: false)
|
8
|
-
args.each { |helper_method| use_helper(helper_method, from: from, prefix: prefix) }
|
9
|
-
end
|
10
|
-
|
11
|
-
def use_helper(helper_method, from: nil, prefix: false)
|
12
|
-
helper_method_name = full_helper_method_name(helper_method, prefix: prefix, source: from)
|
13
|
-
|
14
|
-
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
|
15
|
-
def #{helper_method_name}(...)
|
16
|
-
raise HelpersCalledBeforeRenderError if view_context.nil?
|
17
|
-
|
18
|
-
#{define_helper(helper_method: helper_method, source: from)}
|
19
|
-
end
|
20
|
-
RUBY
|
21
|
-
end
|
22
|
-
|
23
|
-
private
|
24
|
-
|
25
|
-
def full_helper_method_name(helper_method, prefix: false, source: nil)
|
26
|
-
return helper_method unless prefix.present?
|
27
|
-
|
28
|
-
if !!prefix == prefix
|
29
|
-
"#{source.to_s.underscore}_#{helper_method}"
|
30
|
-
else
|
31
|
-
"#{prefix}_#{helper_method}"
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def define_helper(helper_method:, source:)
|
36
|
-
return "__vc_original_view_context.#{helper_method}(...)" unless source.present?
|
37
|
-
|
38
|
-
"#{source}.instance_method(:#{helper_method}).bind(self).call(...)"
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|