view_component 4.0.0.alpha3 → 4.0.0.alpha5
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/docs/CHANGELOG.md +28 -0
- data/lib/view_component/base.rb +52 -47
- data/lib/view_component/config.rb +10 -10
- data/lib/view_component/engine.rb +0 -18
- data/lib/view_component/inline_template.rb +3 -3
- data/lib/view_component/slot.rb +15 -10
- data/lib/view_component/slotable.rb +17 -9
- data/lib/view_component/slotable_default.rb +0 -2
- data/lib/view_component/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 544de384800ad5d0870666b9b1fdb3a74da9fd59d77f78e0d72ee5e134a3c307
|
4
|
+
data.tar.gz: 430c3652f5abc6fef9815b937274f66ebac34de297318e1ca6839589fa290ddf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f30651af979f6c1c5fd43514d04cef3f6430982198e593375d61ce79190e83b8b2d61d7cccb67607248f0b791346b09db33b4705e81cb257b87bd8255c5b2d63
|
7
|
+
data.tar.gz: 956af9c486961785821e9401c9c1ce6b187f3f21a0425d5d6d36602f384f3beb79629f9be2e743e13f525bdc15498480565989cbdb37952a7b0e1cbb2aaabbb0
|
data/docs/CHANGELOG.md
CHANGED
@@ -10,6 +10,34 @@ nav_order: 6
|
|
10
10
|
|
11
11
|
## main
|
12
12
|
|
13
|
+
## 4.0.0.alpha5
|
14
|
+
|
15
|
+
* BREAKING: `config.view_component_path` is now `config.generate.path`, as components have long since been able to exist in any directory.
|
16
|
+
|
17
|
+
*Joel Hawksley*
|
18
|
+
|
19
|
+
* BREAKING: Remove broken integration with `rails stats` that ignored components outside of `app/components`.
|
20
|
+
|
21
|
+
*Joel Hawksley*
|
22
|
+
|
23
|
+
* Add internal optimization for Ruby object shapes.
|
24
|
+
|
25
|
+
*Adam Hess*, *Joel Hawksley*
|
26
|
+
|
27
|
+
## 4.0.0.alpha4
|
28
|
+
|
29
|
+
* BREAKING: 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.
|
30
|
+
|
31
|
+
*Joel Hawksley*
|
32
|
+
|
33
|
+
* Graduate `SlotableDefault` to be included by default.
|
34
|
+
|
35
|
+
*Joel Hawksley*
|
36
|
+
|
37
|
+
* Fix bug in `SlotableDefault` where default couldn't be overridden when content was passed as a block.
|
38
|
+
|
39
|
+
*Bill Watts*, *Joel Hawksley*
|
40
|
+
|
13
41
|
## 4.0.0.alpha3
|
14
42
|
|
15
43
|
* BREAKING: Remove dependency on `ActionView::Base`, eliminating the need for capture compatibility patch.
|
data/lib/view_component/base.rb
CHANGED
@@ -12,7 +12,6 @@ require "view_component/inline_template"
|
|
12
12
|
require "view_component/preview"
|
13
13
|
require "view_component/request_details"
|
14
14
|
require "view_component/slotable"
|
15
|
-
require "view_component/slotable_default"
|
16
15
|
require "view_component/template"
|
17
16
|
require "view_component/translatable"
|
18
17
|
require "view_component/with_content_helper"
|
@@ -36,18 +35,51 @@ module ViewComponent
|
|
36
35
|
class << self
|
37
36
|
delegate(*ViewComponent::Config.defaults.keys, to: :config)
|
38
37
|
|
38
|
+
# Redefine `new` so we can pre-allocate instance variables to optimize
|
39
|
+
# for Ruby object shapes.
|
40
|
+
def new(...)
|
41
|
+
instance = allocate
|
42
|
+
instance.__vc_pre_allocate_instance_variables
|
43
|
+
instance.send(:initialize, ...)
|
44
|
+
instance
|
45
|
+
end
|
46
|
+
|
39
47
|
# Returns the current config.
|
40
48
|
#
|
41
49
|
# @return [ActiveSupport::OrderedOptions]
|
42
50
|
def config
|
43
|
-
module_parents.each do |
|
44
|
-
|
45
|
-
|
51
|
+
module_parents.each do |module_parent|
|
52
|
+
next unless module_parent.respond_to?(:config)
|
53
|
+
module_parent_config = module_parent.config.try(:view_component)
|
54
|
+
return module_parent_config if module_parent_config
|
46
55
|
end
|
47
56
|
ViewComponent::Config.current
|
48
57
|
end
|
49
58
|
end
|
50
59
|
|
60
|
+
def __vc_pre_allocate_instance_variables
|
61
|
+
@__vc_parent_render_level = 0
|
62
|
+
@__vc_set_slots = {}
|
63
|
+
@__vc_content_evaluated = false
|
64
|
+
@current_template = nil
|
65
|
+
@output_buffer = nil
|
66
|
+
@lookup_context = nil
|
67
|
+
@view_flow = nil
|
68
|
+
@view_context = nil
|
69
|
+
@virtual_path = nil
|
70
|
+
@__vc_ancestor_calls = nil
|
71
|
+
@__vc_controller = nil
|
72
|
+
@__vc_content = :unset # some behaviors depend on checking for nil
|
73
|
+
@__vc_content_set_by_with_content = nil
|
74
|
+
@__vc_helpers = nil
|
75
|
+
@__vc_inline_template = nil
|
76
|
+
@__vc_inline_template_defined = nil
|
77
|
+
@__vc_render_in_block = nil
|
78
|
+
@__vc_request = nil
|
79
|
+
@__vc_requested_details = nil
|
80
|
+
@__vc_original_view_context = nil
|
81
|
+
end
|
82
|
+
|
51
83
|
include ActionView::Helpers
|
52
84
|
include ERB::Escape
|
53
85
|
include ActiveSupport::CoreExt::ERBUtil
|
@@ -114,14 +146,12 @@ module ViewComponent
|
|
114
146
|
@__vc_requested_details ||= @lookup_context.vc_requested_details
|
115
147
|
|
116
148
|
# For caching, such as #cache_if
|
117
|
-
@current_template = nil unless defined?(@current_template)
|
118
149
|
old_current_template = @current_template
|
119
150
|
|
120
|
-
if block &&
|
151
|
+
if block && __vc_content_set_by_with_content?
|
121
152
|
raise DuplicateContentError.new(self.class.name)
|
122
153
|
end
|
123
154
|
|
124
|
-
@__vc_content_evaluated = false
|
125
155
|
@__vc_render_in_block = block
|
126
156
|
|
127
157
|
before_render
|
@@ -175,16 +205,12 @@ module ViewComponent
|
|
175
205
|
#
|
176
206
|
# When rendering the parent inside an .erb template, use `#render_parent` instead.
|
177
207
|
def render_parent_to_string
|
178
|
-
@__vc_parent_render_level
|
179
|
-
|
180
|
-
begin
|
181
|
-
target_render = self.class.instance_variable_get(:@__vc_ancestor_calls)[@__vc_parent_render_level]
|
182
|
-
@__vc_parent_render_level += 1
|
208
|
+
target_render = self.class.instance_variable_get(:@__vc_ancestor_calls)[@__vc_parent_render_level]
|
209
|
+
@__vc_parent_render_level += 1
|
183
210
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
end
|
211
|
+
target_render.bind_call(self, @__vc_requested_details)
|
212
|
+
ensure
|
213
|
+
@__vc_parent_render_level -= 1
|
188
214
|
end
|
189
215
|
|
190
216
|
# Optional content to be returned before the rendered template.
|
@@ -216,12 +242,6 @@ module ViewComponent
|
|
216
242
|
true
|
217
243
|
end
|
218
244
|
|
219
|
-
# Override the ActionView::Base initializer so that components
|
220
|
-
# do not need to define their own initializers.
|
221
|
-
# @private
|
222
|
-
def initialize(*)
|
223
|
-
end
|
224
|
-
|
225
245
|
# Re-use original view_context if we're not rendering a component.
|
226
246
|
#
|
227
247
|
# This prevents an exception when rendering a partial inside of a component that has also been rendered outside
|
@@ -274,7 +294,7 @@ module ViewComponent
|
|
274
294
|
raise e, <<~MESSAGE.chomp if view_context && e.is_a?(NameError) && helpers.respond_to?(method_name)
|
275
295
|
#{e.message}
|
276
296
|
|
277
|
-
You may be trying to call a method provided as a view helper. Did you mean `helpers.#{method_name}
|
297
|
+
You may be trying to call a method provided as a view helper. Did you mean `helpers.#{method_name}`?
|
278
298
|
MESSAGE
|
279
299
|
|
280
300
|
raise
|
@@ -313,12 +333,12 @@ module ViewComponent
|
|
313
333
|
# @return [String]
|
314
334
|
def content
|
315
335
|
@__vc_content_evaluated = true
|
316
|
-
return @__vc_content if
|
336
|
+
return @__vc_content if @__vc_content != :unset
|
317
337
|
|
318
338
|
@__vc_content =
|
319
339
|
if __vc_render_in_block_provided?
|
320
340
|
view_context.capture(self, &@__vc_render_in_block)
|
321
|
-
elsif
|
341
|
+
elsif __vc_content_set_by_with_content?
|
322
342
|
@__vc_content_set_by_with_content
|
323
343
|
end
|
324
344
|
end
|
@@ -327,7 +347,7 @@ module ViewComponent
|
|
327
347
|
#
|
328
348
|
# @return [Boolean]
|
329
349
|
def content?
|
330
|
-
__vc_render_in_block_provided? ||
|
350
|
+
__vc_render_in_block_provided? || __vc_content_set_by_with_content?
|
331
351
|
end
|
332
352
|
|
333
353
|
private
|
@@ -335,15 +355,15 @@ module ViewComponent
|
|
335
355
|
attr_reader :view_context
|
336
356
|
|
337
357
|
def __vc_render_in_block_provided?
|
338
|
-
|
358
|
+
@view_context && @__vc_render_in_block
|
339
359
|
end
|
340
360
|
|
341
|
-
def
|
342
|
-
|
361
|
+
def __vc_content_set_by_with_content?
|
362
|
+
!@__vc_content_set_by_with_content.nil?
|
343
363
|
end
|
344
364
|
|
345
365
|
def content_evaluated?
|
346
|
-
|
366
|
+
@__vc_content_evaluated
|
347
367
|
end
|
348
368
|
|
349
369
|
def maybe_escape_html(text)
|
@@ -380,15 +400,6 @@ module ViewComponent
|
|
380
400
|
# configured on a per-test basis using `with_controller_class`.
|
381
401
|
#
|
382
402
|
|
383
|
-
# Path for component files
|
384
|
-
#
|
385
|
-
# ```ruby
|
386
|
-
# config.view_component.view_component_path = "app/my_components"
|
387
|
-
# ```
|
388
|
-
#
|
389
|
-
# Defaults to `nil`. If this is falsy, `app/components` is used.
|
390
|
-
#
|
391
|
-
|
392
403
|
# Parent class for generated components
|
393
404
|
#
|
394
405
|
# ```ruby
|
@@ -558,19 +569,13 @@ module ViewComponent
|
|
558
569
|
# We use `base_label` method here instead of `label` to avoid cases where the method
|
559
570
|
# owner is included in a prefix like `ApplicationComponent.inherited`.
|
560
571
|
child.identifier = caller_locations(1, 10).reject { |l| l.base_label == "inherited" }[0].path
|
561
|
-
|
562
|
-
# If Rails application is loaded, removes the first part of the path and the extension.
|
563
|
-
if defined?(Rails) && Rails.application
|
564
|
-
child.virtual_path = child.identifier.gsub(
|
565
|
-
/(.*#{Regexp.quote(ViewComponent::Base.config.view_component_path)})|(\.rb)/, ""
|
566
|
-
)
|
567
|
-
end
|
572
|
+
child.virtual_path = child.name&.underscore
|
568
573
|
|
569
574
|
# Set collection parameter to the extended component
|
570
575
|
child.with_collection_parameter provided_collection_parameter
|
571
576
|
|
572
577
|
if instance_methods(false).include?(:render_template_for)
|
573
|
-
vc_ancestor_calls =
|
578
|
+
vc_ancestor_calls = (!@__vc_ancestor_calls.nil?) ? @__vc_ancestor_calls.dup : []
|
574
579
|
|
575
580
|
vc_ancestor_calls.unshift(instance_method(:render_template_for))
|
576
581
|
child.instance_variable_set(:@__vc_ancestor_calls, vc_ancestor_calls)
|
@@ -16,7 +16,6 @@ module ViewComponent
|
|
16
16
|
preview_controller: "ViewComponentsController",
|
17
17
|
preview_route: "/rails/view_components",
|
18
18
|
instrumentation_enabled: false,
|
19
|
-
view_component_path: "app/components",
|
20
19
|
component_parent_class: nil,
|
21
20
|
show_previews: Rails.env.development? || Rails.env.test?,
|
22
21
|
preview_paths: default_preview_paths,
|
@@ -32,6 +31,12 @@ module ViewComponent
|
|
32
31
|
# All options under this namespace default to `false` unless otherwise
|
33
32
|
# stated.
|
34
33
|
#
|
34
|
+
# #### `#path`
|
35
|
+
#
|
36
|
+
# Where to put generated components. Defaults to `app/components`:
|
37
|
+
#
|
38
|
+
# config.view_component.generate.path = "lib/components"
|
39
|
+
#
|
35
40
|
# #### `#sidecar`
|
36
41
|
#
|
37
42
|
# Always generate a component with a sidecar directory:
|
@@ -84,14 +89,14 @@ module ViewComponent
|
|
84
89
|
#
|
85
90
|
# #### `#use_component_path_for_rspec_tests`
|
86
91
|
#
|
87
|
-
# Whether to use
|
92
|
+
# Whether to use `config.generate.path` when generating new
|
88
93
|
# RSpec component tests:
|
89
94
|
#
|
90
95
|
# config.view_component.generate.use_component_path_for_rspec_tests = true
|
91
96
|
#
|
92
|
-
# When set to `true`, the generator will use the `
|
97
|
+
# When set to `true`, the generator will use the `path` to
|
93
98
|
# decide where to generate the new RSpec component test.
|
94
|
-
# For example, if the `
|
99
|
+
# For example, if the `path` is
|
95
100
|
# `app/views/components`, then the generator will create a new spec file
|
96
101
|
# in `spec/views/components/` rather than the default `spec/components/`.
|
97
102
|
|
@@ -110,12 +115,6 @@ module ViewComponent
|
|
110
115
|
# Whether ActiveSupport notifications are enabled.
|
111
116
|
# Defaults to `false`.
|
112
117
|
|
113
|
-
# @!attribute view_component_path
|
114
|
-
# @return [String]
|
115
|
-
# The path in which components, their templates, and their sidecars should
|
116
|
-
# be stored.
|
117
|
-
# Defaults to `"app/components"`.
|
118
|
-
|
119
118
|
# @!attribute component_parent_class
|
120
119
|
# @return [String]
|
121
120
|
# The parent class from which generated components will inherit.
|
@@ -171,6 +170,7 @@ module ViewComponent
|
|
171
170
|
def default_generate_options
|
172
171
|
options = ActiveSupport::OrderedOptions.new(false)
|
173
172
|
options.preview_path = ""
|
173
|
+
options.path = "app/components"
|
174
174
|
options
|
175
175
|
end
|
176
176
|
end
|
@@ -8,24 +8,6 @@ module ViewComponent
|
|
8
8
|
class Engine < Rails::Engine # :nodoc:
|
9
9
|
config.view_component = ViewComponent::Config.current
|
10
10
|
|
11
|
-
if Rails.version.to_f < 8.0
|
12
|
-
rake_tasks do
|
13
|
-
load "view_component/rails/tasks/view_component.rake"
|
14
|
-
end
|
15
|
-
else
|
16
|
-
initializer "view_component.stats_directories" do |app|
|
17
|
-
require "rails/code_statistics"
|
18
|
-
|
19
|
-
if Rails.root.join(ViewComponent::Base.view_component_path).directory?
|
20
|
-
Rails::CodeStatistics.register_directory("ViewComponents", ViewComponent::Base.view_component_path)
|
21
|
-
end
|
22
|
-
|
23
|
-
if Rails.root.join("test/components").directory?
|
24
|
-
Rails::CodeStatistics.register_directory("ViewComponent tests", "test/components", test_directory: true)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
11
|
initializer "view_component.set_configs" do |app|
|
30
12
|
options = app.config.view_component
|
31
13
|
|
@@ -9,7 +9,7 @@ module ViewComponent # :nodoc:
|
|
9
9
|
def method_missing(method, *args)
|
10
10
|
return super if !method.end_with?("_template")
|
11
11
|
|
12
|
-
if
|
12
|
+
if @__vc_inline_template_defined
|
13
13
|
raise MultipleInlineTemplatesError
|
14
14
|
end
|
15
15
|
|
@@ -38,11 +38,11 @@ module ViewComponent # :nodoc:
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def inline_template
|
41
|
-
@__vc_inline_template
|
41
|
+
@__vc_inline_template
|
42
42
|
end
|
43
43
|
|
44
44
|
def __vc_inline_template_language
|
45
|
-
@__vc_inline_template_language
|
45
|
+
@__vc_inline_template_language
|
46
46
|
end
|
47
47
|
|
48
48
|
def inherited(subclass)
|
data/lib/view_component/slot.rb
CHANGED
@@ -9,13 +9,18 @@ module ViewComponent
|
|
9
9
|
attr_writer :__vc_component_instance, :__vc_content_block, :__vc_content
|
10
10
|
|
11
11
|
def initialize(parent)
|
12
|
+
@content = nil
|
13
|
+
@__vc_component_instance = nil
|
14
|
+
@__vc_content = nil
|
15
|
+
@__vc_content_block = nil
|
16
|
+
@__vc_content_set_by_with_content = nil
|
12
17
|
@parent = parent
|
13
18
|
end
|
14
19
|
|
15
20
|
def content?
|
16
|
-
return true if
|
17
|
-
return true if
|
18
|
-
return true if
|
21
|
+
return true if @__vc_content.present?
|
22
|
+
return true if @__vc_content_set_by_with_content.present?
|
23
|
+
return true if @__vc_content_block.present?
|
19
24
|
return false if !__vc_component_instance?
|
20
25
|
|
21
26
|
@__vc_component_instance.content?
|
@@ -43,11 +48,11 @@ module ViewComponent
|
|
43
48
|
# If there is no slot renderable, we evaluate the block passed to
|
44
49
|
# the slot and return it.
|
45
50
|
def to_s
|
46
|
-
return @content if
|
51
|
+
return @content if !@content.nil?
|
47
52
|
|
48
53
|
view_context = @parent.send(:view_context)
|
49
54
|
|
50
|
-
if
|
55
|
+
if !@__vc_content_block.nil? && !@__vc_content_set_by_with_content.nil? && !@__vc_content_set_by_with_content.nil?
|
51
56
|
raise DuplicateSlotContentError.new(self.class.name)
|
52
57
|
end
|
53
58
|
|
@@ -55,7 +60,7 @@ module ViewComponent
|
|
55
60
|
if __vc_component_instance?
|
56
61
|
@__vc_component_instance.__vc_original_view_context = @parent.__vc_original_view_context
|
57
62
|
|
58
|
-
if
|
63
|
+
if !@__vc_content_block.nil?
|
59
64
|
# render_in is faster than `parent.render`
|
60
65
|
@__vc_component_instance.render_in(view_context) do |*args|
|
61
66
|
@__vc_content_block.call(*args)
|
@@ -63,11 +68,11 @@ module ViewComponent
|
|
63
68
|
else
|
64
69
|
@__vc_component_instance.render_in(view_context)
|
65
70
|
end
|
66
|
-
elsif
|
71
|
+
elsif !@__vc_content.nil?
|
67
72
|
@__vc_content
|
68
|
-
elsif
|
73
|
+
elsif !@__vc_content_block.nil?
|
69
74
|
view_context.capture(&@__vc_content_block)
|
70
|
-
elsif
|
75
|
+
elsif !@__vc_content_set_by_with_content.nil?
|
71
76
|
@__vc_content_set_by_with_content
|
72
77
|
end
|
73
78
|
|
@@ -108,7 +113,7 @@ module ViewComponent
|
|
108
113
|
private
|
109
114
|
|
110
115
|
def __vc_component_instance?
|
111
|
-
|
116
|
+
!@__vc_component_instance.nil?
|
112
117
|
end
|
113
118
|
end
|
114
119
|
end
|
@@ -351,16 +351,26 @@ module ViewComponent
|
|
351
351
|
end
|
352
352
|
|
353
353
|
def get_slot(slot_name)
|
354
|
+
@__vc_set_slots ||= {}
|
354
355
|
content unless content_evaluated? # ensure content is loaded so slots will be defined
|
355
356
|
|
356
|
-
slot
|
357
|
-
@__vc_set_slots
|
357
|
+
# If the slot is set, return it
|
358
|
+
return @__vc_set_slots[slot_name] if @__vc_set_slots[slot_name]
|
358
359
|
|
359
|
-
|
360
|
-
|
361
|
-
|
360
|
+
# If there is a default method for the slot, call it
|
361
|
+
if (default_method = registered_slots[slot_name][:default_method])
|
362
|
+
renderable_value = send(default_method)
|
363
|
+
slot = Slot.new(self)
|
364
|
+
|
365
|
+
if renderable_value.respond_to?(:render_in)
|
366
|
+
slot.__vc_component_instance = renderable_value
|
367
|
+
else
|
368
|
+
slot.__vc_content = renderable_value
|
369
|
+
end
|
362
370
|
|
363
|
-
|
371
|
+
slot
|
372
|
+
elsif self.class.registered_slots[slot_name][:collection]
|
373
|
+
# If empty slot is a collection, return an empty array
|
364
374
|
[]
|
365
375
|
end
|
366
376
|
end
|
@@ -410,8 +420,6 @@ module ViewComponent
|
|
410
420
|
end
|
411
421
|
end
|
412
422
|
|
413
|
-
@__vc_set_slots ||= {}
|
414
|
-
|
415
423
|
if slot_definition[:collection]
|
416
424
|
@__vc_set_slots[slot_name] ||= []
|
417
425
|
@__vc_set_slots[slot_name].push(slot)
|
@@ -425,7 +433,7 @@ module ViewComponent
|
|
425
433
|
def set_polymorphic_slot(slot_name, poly_type = nil, *args, **kwargs, &block)
|
426
434
|
slot_definition = self.class.registered_slots[slot_name]
|
427
435
|
|
428
|
-
if !slot_definition[:collection] &&
|
436
|
+
if !slot_definition[:collection] && @__vc_set_slots[slot_name]
|
429
437
|
raise ContentAlreadySetForPolymorphicSlotError.new(slot_name)
|
430
438
|
end
|
431
439
|
|