view_component 2.41.0 → 2.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of view_component might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/docs/CHANGELOG.md +26 -2
- data/lib/rails/generators/stimulus/component_generator.rb +11 -0
- data/lib/rails/generators/stimulus/templates/component_controller.js.tt +1 -1
- data/lib/view_component/base.rb +15 -2
- data/lib/view_component/polymorphic_slots.rb +74 -0
- data/lib/view_component/slot_v2.rb +3 -8
- data/lib/view_component/slotable_v2.rb +32 -20
- data/lib/view_component/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ec259ffe370fa7001ca5669ff70484a339564e113b5adcde81862873375a91f3
|
4
|
+
data.tar.gz: a1ffead994abcb10cf05a39a5d6cdadbfcc7987c19682858356b16574cda5bc9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 43b9b1daceb39a542bf2923bf33420c3c3bce34188190393f54982eef65fa3f6bd27315b07ac86fc5fef95847182eeae1994f32e68611b08839287569808e43b
|
7
|
+
data.tar.gz: 761538016609ef056dd3940bc84e2fa425c42a98bfd860f9123b368a75c2c3a6ece671c3fd8454a5e2e754a34d10b30448e5f41dc4f679ab7e38ef7e9b0277a4
|
data/README.md
CHANGED
data/docs/CHANGELOG.md
CHANGED
@@ -5,7 +5,31 @@ title: Changelog
|
|
5
5
|
|
6
6
|
# Changelog
|
7
7
|
|
8
|
-
##
|
8
|
+
## 2.42.0
|
9
|
+
|
10
|
+
* Add logo files and page to docs.
|
11
|
+
|
12
|
+
*Dylan Smith*
|
13
|
+
|
14
|
+
* Add `ViewComponents in practice` documentation.
|
15
|
+
|
16
|
+
*Joel Hawksley*
|
17
|
+
|
18
|
+
* Fix bug where calling lambda slots without arguments would break in Ruby < 2.7.
|
19
|
+
|
20
|
+
*Manuel Puyol*
|
21
|
+
|
22
|
+
* Improve Stimulus controller template to import from `stimulus` or `@hotwired/stimulus`.
|
23
|
+
|
24
|
+
*Mario Schüttel*
|
25
|
+
|
26
|
+
* Fix bug where `helpers` would instantiate and use a new `view_context` in each component.
|
27
|
+
|
28
|
+
*Blake Williams*, *Ian C. Anderson*
|
29
|
+
|
30
|
+
* Implement polymorphic slots as experimental feature. See the Slots documentation to learn more.
|
31
|
+
|
32
|
+
*Cameron Dutro*
|
9
33
|
|
10
34
|
## 2.41.0
|
11
35
|
|
@@ -42,7 +66,7 @@ title: Changelog
|
|
42
66
|
|
43
67
|
*Hans Lemuet*
|
44
68
|
|
45
|
-
* Forward keyword arguments from slot wrapper to component instance.
|
69
|
+
* Forward keyword arguments from slot wrapper to component instance using ruby2_keywords.
|
46
70
|
|
47
71
|
*Cameron Dutro*
|
48
72
|
|
@@ -12,6 +12,12 @@ module Stimulus
|
|
12
12
|
template "component_controller.js", destination
|
13
13
|
end
|
14
14
|
|
15
|
+
def stimulus_module
|
16
|
+
return "stimulus" if legacy_stimulus?
|
17
|
+
|
18
|
+
"@hotwired/stimulus"
|
19
|
+
end
|
20
|
+
|
15
21
|
private
|
16
22
|
|
17
23
|
def destination
|
@@ -21,6 +27,11 @@ module Stimulus
|
|
21
27
|
File.join(component_path, class_path, "#{file_name}_component_controller.js")
|
22
28
|
end
|
23
29
|
end
|
30
|
+
|
31
|
+
def legacy_stimulus?
|
32
|
+
package_json_pathname = Rails.root.join("package.json")
|
33
|
+
package_json_pathname.exist? && JSON.parse(package_json_pathname.read).dig("dependencies", "stimulus").present?
|
34
|
+
end
|
24
35
|
end
|
25
36
|
end
|
26
37
|
end
|
data/lib/view_component/base.rb
CHANGED
@@ -5,6 +5,7 @@ require "active_support/configurable"
|
|
5
5
|
require "view_component/collection"
|
6
6
|
require "view_component/compile_cache"
|
7
7
|
require "view_component/content_areas"
|
8
|
+
require "view_component/polymorphic_slots"
|
8
9
|
require "view_component/previewable"
|
9
10
|
require "view_component/slotable"
|
10
11
|
require "view_component/slotable_v2"
|
@@ -28,6 +29,8 @@ module ViewComponent
|
|
28
29
|
class_attribute :content_areas
|
29
30
|
self.content_areas = [] # class_attribute:default doesn't work until Rails 5.2
|
30
31
|
|
32
|
+
attr_accessor :original_view_context
|
33
|
+
|
31
34
|
# EXPERIMENTAL: This API is experimental and may be removed at any time.
|
32
35
|
# Hook for allowing components to do work as part of the compilation process.
|
33
36
|
#
|
@@ -49,6 +52,8 @@ module ViewComponent
|
|
49
52
|
self.class.compile(raise_errors: true)
|
50
53
|
|
51
54
|
@view_context = view_context
|
55
|
+
self.original_view_context ||= view_context
|
56
|
+
|
52
57
|
@lookup_context ||= view_context.lookup_context
|
53
58
|
|
54
59
|
# required for path helpers in older Rails versions
|
@@ -132,9 +137,10 @@ module ViewComponent
|
|
132
137
|
# @private
|
133
138
|
def render(options = {}, args = {}, &block)
|
134
139
|
if options.is_a? ViewComponent::Base
|
140
|
+
options.original_view_context = original_view_context
|
135
141
|
super
|
136
142
|
else
|
137
|
-
|
143
|
+
original_view_context.render(options, args, &block)
|
138
144
|
end
|
139
145
|
end
|
140
146
|
|
@@ -173,7 +179,14 @@ module ViewComponent
|
|
173
179
|
)
|
174
180
|
end
|
175
181
|
|
176
|
-
|
182
|
+
# Attempt to re-use the original view_context passed to the first
|
183
|
+
# component rendered in the rendering pipeline. This prevents the
|
184
|
+
# instantiation of a new view_context via `controller.view_context` which
|
185
|
+
# always returns a new instance of the view context class.
|
186
|
+
#
|
187
|
+
# This allows ivars to remain persisted when using the same helper via
|
188
|
+
# `helpers` across multiple components and partials.
|
189
|
+
@__vc_helpers ||= original_view_context || controller.view_context
|
177
190
|
end
|
178
191
|
|
179
192
|
# Exposes .virtual_path as an instance method
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ViewComponent
|
4
|
+
module PolymorphicSlots
|
5
|
+
# In older rails versions, using a concern isn't a good idea here because they appear to not work with
|
6
|
+
# Module#prepend and class methods.
|
7
|
+
def self.included(base)
|
8
|
+
base.singleton_class.prepend(ClassMethods)
|
9
|
+
base.include(InstanceMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def renders_one(slot_name, callable = nil)
|
14
|
+
return super unless callable.is_a?(Hash) && callable.key?(:types)
|
15
|
+
|
16
|
+
validate_singular_slot_name(slot_name)
|
17
|
+
register_polymorphic_slot(slot_name, callable[:types], collection: false)
|
18
|
+
end
|
19
|
+
|
20
|
+
def renders_many(slot_name, callable = nil)
|
21
|
+
return super unless callable.is_a?(Hash) && callable.key?(:types)
|
22
|
+
|
23
|
+
validate_plural_slot_name(slot_name)
|
24
|
+
register_polymorphic_slot(slot_name, callable[:types], collection: true)
|
25
|
+
end
|
26
|
+
|
27
|
+
def register_polymorphic_slot(slot_name, types, collection:)
|
28
|
+
renderable_hash = types.each_with_object({}) do |(poly_type, poly_callable), memo|
|
29
|
+
memo[poly_type] = define_slot(
|
30
|
+
"#{slot_name}_#{poly_type}", collection: collection, callable: poly_callable
|
31
|
+
)
|
32
|
+
|
33
|
+
getter_name = slot_name
|
34
|
+
setter_name =
|
35
|
+
if collection
|
36
|
+
"#{ActiveSupport::Inflector.singularize(slot_name)}_#{poly_type}"
|
37
|
+
else
|
38
|
+
"#{slot_name}_#{poly_type}"
|
39
|
+
end
|
40
|
+
|
41
|
+
define_method(getter_name) do
|
42
|
+
get_slot(slot_name)
|
43
|
+
end
|
44
|
+
ruby2_keywords(getter_name.to_sym) if respond_to?(:ruby2_keywords, true)
|
45
|
+
|
46
|
+
define_method(setter_name) do |*args, &block|
|
47
|
+
set_polymorphic_slot(slot_name, poly_type, *args, &block)
|
48
|
+
end
|
49
|
+
ruby2_keywords(setter_name.to_sym) if respond_to?(:ruby2_keywords, true)
|
50
|
+
end
|
51
|
+
|
52
|
+
self.registered_slots[slot_name] = {
|
53
|
+
collection: collection,
|
54
|
+
renderable_hash: renderable_hash
|
55
|
+
}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
module InstanceMethods
|
60
|
+
def set_polymorphic_slot(slot_name, poly_type = nil, *args, &block)
|
61
|
+
slot_definition = self.class.registered_slots[slot_name]
|
62
|
+
|
63
|
+
if !slot_definition[:collection] && get_slot(slot_name)
|
64
|
+
raise ArgumentError, "content for slot '#{slot_name}' has already been provided"
|
65
|
+
end
|
66
|
+
|
67
|
+
poly_def = slot_definition[:renderable_hash][poly_type]
|
68
|
+
|
69
|
+
set_slot(slot_name, poly_def, *args, &block)
|
70
|
+
end
|
71
|
+
ruby2_keywords(:set_polymorphic_slot) if respond_to?(:ruby2_keywords, true)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -86,15 +86,10 @@ module ViewComponent
|
|
86
86
|
# end
|
87
87
|
# end
|
88
88
|
#
|
89
|
-
|
90
|
-
|
91
|
-
@__vc_component_instance.public_send(symbol, *args, **kwargs, &block)
|
92
|
-
end
|
93
|
-
else
|
94
|
-
def method_missing(symbol, *args, &block)
|
95
|
-
@__vc_component_instance.public_send(symbol, *args, &block)
|
96
|
-
end
|
89
|
+
def method_missing(symbol, *args, &block)
|
90
|
+
@__vc_component_instance.public_send(symbol, *args, &block)
|
97
91
|
end
|
92
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
98
93
|
|
99
94
|
def html_safe?
|
100
95
|
to_s.html_safe?
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support/concern"
|
4
|
-
|
5
4
|
require "view_component/slot_v2"
|
6
5
|
|
7
6
|
module ViewComponent
|
@@ -67,13 +66,14 @@ module ViewComponent
|
|
67
66
|
def renders_one(slot_name, callable = nil)
|
68
67
|
validate_singular_slot_name(slot_name)
|
69
68
|
|
70
|
-
define_method slot_name do |*args,
|
71
|
-
if args.empty? &&
|
69
|
+
define_method slot_name do |*args, &block|
|
70
|
+
if args.empty? && block.nil?
|
72
71
|
get_slot(slot_name)
|
73
72
|
else
|
74
|
-
set_slot(slot_name, *args,
|
73
|
+
set_slot(slot_name, nil, *args, &block)
|
75
74
|
end
|
76
75
|
end
|
76
|
+
ruby2_keywords(slot_name.to_sym) if respond_to?(:ruby2_keywords, true)
|
77
77
|
|
78
78
|
register_slot(slot_name, collection: false, callable: callable)
|
79
79
|
end
|
@@ -123,9 +123,10 @@ module ViewComponent
|
|
123
123
|
# Define setter for singular names
|
124
124
|
# e.g. `renders_many :items` allows fetching all tabs with
|
125
125
|
# `component.tabs` and setting a tab with `component.tab`
|
126
|
-
define_method singular_name do |*args,
|
127
|
-
set_slot(slot_name, *args,
|
126
|
+
define_method singular_name do |*args, &block|
|
127
|
+
set_slot(slot_name, nil, *args, &block)
|
128
128
|
end
|
129
|
+
ruby2_keywords(singular_name.to_sym) if respond_to?(:ruby2_keywords, true)
|
129
130
|
|
130
131
|
# Instantiates and and adds multiple slots forwarding the first
|
131
132
|
# argument to each slot constructor
|
@@ -134,7 +135,7 @@ module ViewComponent
|
|
134
135
|
get_slot(slot_name)
|
135
136
|
else
|
136
137
|
collection_args.map do |args|
|
137
|
-
set_slot(slot_name, **args, &block)
|
138
|
+
set_slot(slot_name, nil, **args, &block)
|
138
139
|
end
|
139
140
|
end
|
140
141
|
end
|
@@ -162,27 +163,37 @@ module ViewComponent
|
|
162
163
|
|
163
164
|
private
|
164
165
|
|
165
|
-
def register_slot(slot_name,
|
166
|
+
def register_slot(slot_name, **kwargs)
|
167
|
+
self.registered_slots[slot_name] = define_slot(slot_name, **kwargs)
|
168
|
+
end
|
169
|
+
|
170
|
+
def define_slot(slot_name, collection:, callable:)
|
166
171
|
# Setup basic slot data
|
167
172
|
slot = {
|
168
173
|
collection: collection,
|
169
174
|
}
|
175
|
+
return slot unless callable
|
176
|
+
|
170
177
|
# If callable responds to `render_in`, we set it on the slot as a renderable
|
171
|
-
if callable
|
178
|
+
if callable.respond_to?(:method_defined?) && callable.method_defined?(:render_in)
|
172
179
|
slot[:renderable] = callable
|
173
180
|
elsif callable.is_a?(String)
|
174
181
|
# If callable is a string, we assume it's referencing an internal class
|
175
182
|
slot[:renderable_class_name] = callable
|
176
|
-
elsif callable
|
183
|
+
elsif callable.respond_to?(:call)
|
177
184
|
# If slot does not respond to `render_in`, we assume it's a proc,
|
178
185
|
# define a method, and save a reference to it to call when setting
|
179
186
|
method_name = :"_call_#{slot_name}"
|
180
187
|
define_method method_name, &callable
|
181
188
|
slot[:renderable_function] = instance_method(method_name)
|
189
|
+
else
|
190
|
+
raise(
|
191
|
+
ArgumentError,
|
192
|
+
"invalid slot definition. Please pass a class, string, or callable (i.e. proc, lambda, etc)"
|
193
|
+
)
|
182
194
|
end
|
183
195
|
|
184
|
-
|
185
|
-
self.registered_slots[slot_name] = slot
|
196
|
+
slot
|
186
197
|
end
|
187
198
|
|
188
199
|
def validate_plural_slot_name(slot_name)
|
@@ -235,9 +246,8 @@ module ViewComponent
|
|
235
246
|
end
|
236
247
|
end
|
237
248
|
|
238
|
-
def set_slot(slot_name, *args,
|
239
|
-
slot_definition
|
240
|
-
|
249
|
+
def set_slot(slot_name, slot_definition = nil, *args, &block)
|
250
|
+
slot_definition ||= self.class.registered_slots[slot_name]
|
241
251
|
slot = SlotV2.new(self)
|
242
252
|
|
243
253
|
# Passing the block to the sub-component wrapper like this has two
|
@@ -253,23 +263,24 @@ module ViewComponent
|
|
253
263
|
|
254
264
|
# If class
|
255
265
|
if slot_definition[:renderable]
|
256
|
-
slot.__vc_component_instance = slot_definition[:renderable].new(*args
|
266
|
+
slot.__vc_component_instance = slot_definition[:renderable].new(*args)
|
257
267
|
# If class name as a string
|
258
268
|
elsif slot_definition[:renderable_class_name]
|
259
269
|
slot.__vc_component_instance =
|
260
|
-
self.class.const_get(slot_definition[:renderable_class_name]).new(*args
|
270
|
+
self.class.const_get(slot_definition[:renderable_class_name]).new(*args)
|
261
271
|
# If passed a lambda
|
262
272
|
elsif slot_definition[:renderable_function]
|
263
273
|
# Use `bind(self)` to ensure lambda is executed in the context of the
|
264
274
|
# current component. This is necessary to allow the lambda to access helper
|
265
275
|
# methods like `content_tag` as well as parent component state.
|
276
|
+
renderable_function = slot_definition[:renderable_function].bind(self)
|
266
277
|
renderable_value =
|
267
278
|
if block_given?
|
268
|
-
|
269
|
-
view_context.capture(*args,
|
279
|
+
renderable_function.call(*args) do |*args|
|
280
|
+
view_context.capture(*args, &block)
|
270
281
|
end
|
271
282
|
else
|
272
|
-
|
283
|
+
renderable_function.call(*args)
|
273
284
|
end
|
274
285
|
|
275
286
|
# Function calls can return components, so if it's a component handle it specially
|
@@ -291,5 +302,6 @@ module ViewComponent
|
|
291
302
|
|
292
303
|
slot
|
293
304
|
end
|
305
|
+
ruby2_keywords(:set_slot) if respond_to?(:ruby2_keywords, true)
|
294
306
|
end
|
295
307
|
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.42.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- GitHub Open Source
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-10-
|
11
|
+
date: 2021-10-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -314,6 +314,7 @@ files:
|
|
314
314
|
- lib/view_component/content_areas.rb
|
315
315
|
- lib/view_component/engine.rb
|
316
316
|
- lib/view_component/instrumentation.rb
|
317
|
+
- lib/view_component/polymorphic_slots.rb
|
317
318
|
- lib/view_component/preview.rb
|
318
319
|
- lib/view_component/preview_template_error.rb
|
319
320
|
- lib/view_component/previewable.rb
|
@@ -355,7 +356,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
355
356
|
- !ruby/object:Gem::Version
|
356
357
|
version: '0'
|
357
358
|
requirements: []
|
358
|
-
rubygems_version: 3.1.
|
359
|
+
rubygems_version: 3.1.2
|
359
360
|
signing_key:
|
360
361
|
specification_version: 4
|
361
362
|
summary: View components for Rails
|