view_component 2.23.1 → 2.26.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b2282359cc779d83fa6aa65b912fe4e4e70d2dbd6c8939b2bd0f3e21bd2eb9e7
4
- data.tar.gz: c97fffc33ed1ffb6cd4598136c30c1b5990f1f150e8a7fc04ecfae933ae6c271
3
+ metadata.gz: 5a1fb023bfaa4a1ea26166ddd22b5fcd4a7e5f5da1bc7f10039c2bc557f1a94c
4
+ data.tar.gz: 5274b49f0e18f521b15181c70aeb4edcf3458cba13dc8c8ea59b9331d26681c5
5
5
  SHA512:
6
- metadata.gz: ec8397db82e97af474d4d25c8fde9346c73de00fdc37cd108640983a3230c08eae4c95566e8c86a96ad78c7e0c04aabf028f3f9c3e3040111d2b84d43c92bd53
7
- data.tar.gz: 152e91807eff69a99066d20d977ec9092c71f44754fa2f2fd76cd69be4f221e47cdfd01c413aaac60f7334c4d852f86a2a8514f395c8ed773407203c82d77fe0
6
+ metadata.gz: fc60826197c5dbd54ebd53517ff59eab097449913802a4e17df9db33ab54d1daf8ff2e00d1629058962c267274ec7fe6e362aa036206d379e27c7c832db7d05c
7
+ data.tar.gz: b3a65f7a829d9e9fc675904d61c6d1c1015c194c5a7abe596961b3cb20a3a4c2045f1ce49b1b98a551c4b745688c38610d3a86040c1f7f0229c3b247a51f9c65
data/CHANGELOG.md CHANGED
@@ -1,6 +1,60 @@
1
1
  # CHANGELOG
2
2
 
3
- ## master
3
+ ## main
4
+
5
+ ## 2.26.0
6
+
7
+ * Lazily evaluate component `content` in `render?`, preventing the `content` block from being evaluated when `render?` returns false.
8
+
9
+ *Blake Williams*
10
+
11
+ * Do not generate template when using `--inline` flag.
12
+
13
+ *Hans Lemuet*
14
+
15
+ * Add `--inline` option to the Haml and Slim generators
16
+
17
+ *Hans Lemuet*
18
+
19
+ ## 2.25.1
20
+
21
+ * Experimental: call `._after_compile` class method after a component is compiled.
22
+
23
+ *Joel Hawksley*
24
+
25
+ * Fix bug where SlotV2 was rendered as an HTML string when using Slim.
26
+
27
+ *Manuel Puyol*
28
+
29
+ ## 2.25.0
30
+
31
+ * Add `--preview` generator option to create an associated preview file.
32
+
33
+ *Bob Maerten*
34
+
35
+ * Add argument validation to avoid `content` override.
36
+
37
+ *Manuel Puyol*
38
+
39
+ ## 2.24.0
40
+
41
+ * Add `--inline` option to the erb generator. Prevents default erb template from being created and creates a component with a call method.
42
+
43
+ *Nachiket Pusalkar*
44
+
45
+ * Add test case for checking presence of `content` in `#render?`.
46
+
47
+ *Joel Hawksley*
48
+
49
+ * Rename `master` branch to `main`.
50
+
51
+ *Joel Hawksley*
52
+
53
+ ## 2.23.2
54
+
55
+ * Fix bug where rendering a component `with_collection` from a controller raised an error.
56
+
57
+ *Joel Hawksley*
4
58
 
5
59
  ## 2.23.1
6
60
 
@@ -7,6 +7,7 @@ module Rails
7
7
 
8
8
  argument :attributes, type: :array, default: [], banner: "attribute"
9
9
  check_class_collision suffix: "Component"
10
+ class_option :inline, type: :boolean, default: false
10
11
 
11
12
  def create_component_file
12
13
  template "component.rb", File.join("app/components", class_path, "#{file_name}_component.rb")
@@ -14,6 +15,8 @@ module Rails
14
15
 
15
16
  hook_for :test_framework
16
17
 
18
+ hook_for :preview, type: :boolean
19
+
17
20
  hook_for :template_engine do |instance, template_engine|
18
21
  instance.invoke template_engine, [instance.name]
19
22
  end
@@ -37,6 +40,10 @@ module Rails
37
40
  def initialize_body
38
41
  attributes.map { |attr| "@#{attr.name} = #{attr.name}" }.join("\n ")
39
42
  end
43
+
44
+ def initialize_call_method_for_inline?
45
+ options["inline"]
46
+ end
40
47
  end
41
48
  end
42
49
  end
@@ -6,4 +6,10 @@ class <%= class_name %>Component < <%= parent_class %>
6
6
  <%= initialize_body %>
7
7
  end
8
8
  <%- end -%>
9
+ <%- if initialize_call_method_for_inline? -%>
10
+ def call
11
+ content_tag :h1, "Hello world!"
12
+ end
13
+ <%- end -%>
14
+
9
15
  end
@@ -7,9 +7,12 @@ module Erb
7
7
  class ComponentGenerator < Base
8
8
  source_root File.expand_path("templates", __dir__)
9
9
  class_option :sidecar, type: :boolean, default: false
10
+ class_option :inline, type: :boolean, default: false
10
11
 
11
12
  def copy_view_file
12
- template "component.html.erb", destination
13
+ unless options["inline"]
14
+ template "component.html.erb", destination
15
+ end
13
16
  end
14
17
 
15
18
  private
@@ -9,7 +9,9 @@ module Haml
9
9
  class_option :sidecar, type: :boolean, default: false
10
10
 
11
11
  def copy_view_file
12
- template "component.html.haml", destination
12
+ if !options["inline"]
13
+ template "component.html.haml", destination
14
+ end
13
15
  end
14
16
 
15
17
  private
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Preview
4
+ module Generators
5
+ class ComponentGenerator < ::Rails::Generators::NamedBase
6
+ source_root File.expand_path("templates", __dir__)
7
+
8
+ check_class_collision suffix: "ComponentPreview"
9
+
10
+ def create_preview_file
11
+ template "component_preview.rb", File.join("test/components/previews", class_path, "#{file_name}_component_preview.rb")
12
+ end
13
+
14
+ private
15
+
16
+ def file_name
17
+ @_file_name ||= super.sub(/_component\z/i, "")
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,5 @@
1
+ class <%= class_name %>ComponentPreview < ViewComponent::Preview
2
+ def default
3
+ render(<%= class_name %>Component.new)
4
+ end
5
+ end
@@ -9,7 +9,9 @@ module Slim
9
9
  class_option :sidecar, type: :boolean, default: false
10
10
 
11
11
  def copy_view_file
12
- template "component.html.slim", destination
12
+ if !options["inline"]
13
+ template "component.html.slim", destination
14
+ end
13
15
  end
14
16
 
15
17
  private
@@ -15,12 +15,22 @@ module ViewComponent
15
15
 
16
16
  ViewContextCalledBeforeRenderError = Class.new(StandardError)
17
17
 
18
+ RESERVED_PARAMETER = :content
19
+
18
20
  # For CSRF authenticity tokens in forms
19
21
  delegate :form_authenticity_token, :protect_against_forgery?, :config, to: :helpers
20
22
 
21
23
  class_attribute :content_areas
22
24
  self.content_areas = [] # class_attribute:default doesn't work until Rails 5.2
23
25
 
26
+ # EXPERIMENTAL: This API is experimental and may be removed at any time.
27
+ # Hook for allowing components to do work as part of the compilation process.
28
+ #
29
+ # For example, one might compile component-specific assets at this point.
30
+ def self._after_compile
31
+ # noop
32
+ end
33
+
24
34
  # Entrypoint for rendering components.
25
35
  #
26
36
  # view_context: ActionView context from calling view
@@ -68,8 +78,8 @@ module ViewComponent
68
78
  old_current_template = @current_template
69
79
  @current_template = self
70
80
 
71
- # Assign captured content passed to component as a block to @content
72
- @content = view_context.capture(self, &block) if block_given?
81
+ @_content_evaluated = false
82
+ @_render_in_block = block
73
83
 
74
84
  before_render
75
85
 
@@ -167,7 +177,20 @@ module ViewComponent
167
177
  @request ||= controller.request
168
178
  end
169
179
 
170
- attr_reader :content, :view_context
180
+ attr_reader :view_context
181
+
182
+ def content
183
+ return @_content if defined?(@_content)
184
+ @_content_evaluated = true
185
+
186
+ @_content = if @view_context && @_render_in_block
187
+ view_context.capture(self, &@_render_in_block)
188
+ end
189
+ end
190
+
191
+ def content_evaluated?
192
+ @_content_evaluated
193
+ end
171
194
 
172
195
  # The controller used for testing components.
173
196
  # Defaults to ApplicationController. This should be set early
@@ -246,7 +269,14 @@ module ViewComponent
246
269
  if areas.include?(:content)
247
270
  raise ArgumentError.new ":content is a reserved content area name. Please use another name, such as ':body'"
248
271
  end
249
- attr_reader(*areas)
272
+
273
+ areas.each do |area|
274
+ define_method area.to_sym do
275
+ content unless content_evaluated? # ensure content is loaded so content_areas will be defined
276
+ instance_variable_get(:"@#{area}") if instance_variable_defined?(:"@#{area}")
277
+ end
278
+ end
279
+
250
280
  self.content_areas = areas
251
281
  end
252
282
 
@@ -264,7 +294,7 @@ module ViewComponent
264
294
  parameter = validate_default ? collection_parameter : provided_collection_parameter
265
295
 
266
296
  return unless parameter
267
- return if initialize_parameters.map(&:last).include?(parameter)
297
+ return if initialize_parameter_names.include?(parameter)
268
298
 
269
299
  # If Ruby cannot parse the component class, then the initalize
270
300
  # parameters will be empty and ViewComponent will not be able to render
@@ -281,8 +311,25 @@ module ViewComponent
281
311
  )
282
312
  end
283
313
 
314
+ # Ensure the component initializer does not define
315
+ # invalid parameters that could override the framework's
316
+ # methods.
317
+ def validate_initialization_parameters!
318
+ return unless initialize_parameter_names.include?(RESERVED_PARAMETER)
319
+
320
+ raise ArgumentError.new(
321
+ "#{self} initializer cannot contain " \
322
+ "`#{RESERVED_PARAMETER}` since it will override a " \
323
+ "public ViewComponent method."
324
+ )
325
+ end
326
+
284
327
  private
285
328
 
329
+ def initialize_parameter_names
330
+ initialize_parameters.map(&:last)
331
+ end
332
+
286
333
  def initialize_parameters
287
334
  instance_method(:initialize).parameters
288
335
  end
@@ -4,14 +4,16 @@ require "action_view/renderer/collection_renderer" if Rails.version.to_f >= 6.1
4
4
 
5
5
  module ViewComponent
6
6
  class Collection
7
+ attr_reader :component
8
+ delegate :format, to: :component
9
+
7
10
  def render_in(view_context, &block)
8
11
  iterator = ActionView::PartialIteration.new(@collection.size)
9
12
 
10
- @component.compile(raise_errors: true)
11
- @component.validate_collection_parameter!(validate_default: true)
13
+ component.validate_collection_parameter!(validate_default: true)
12
14
 
13
15
  @collection.map do |item|
14
- content = @component.new(**component_options(item, iterator)).render_in(view_context, &block)
16
+ content = component.new(**component_options(item, iterator)).render_in(view_context, &block)
15
17
  iterator.iterate!
16
18
  content
17
19
  end.join.html_safe
@@ -34,8 +36,8 @@ module ViewComponent
34
36
  end
35
37
 
36
38
  def component_options(item, iterator)
37
- item_options = { @component.collection_parameter => item }
38
- item_options[@component.collection_counter_parameter] = iterator.index + 1 if @component.counter_argument_present?
39
+ item_options = { component.collection_parameter => item }
40
+ item_options[component.collection_counter_parameter] = iterator.index + 1 if component.counter_argument_present?
39
41
 
40
42
  @options.merge(item_options)
41
43
  end
@@ -46,7 +46,10 @@ module ViewComponent
46
46
  instance_method(:initialize).parameters.map(&:second).include?(collection_counter_parameter)
47
47
  end
48
48
 
49
- component_class.validate_collection_parameter! if raise_errors
49
+ if raise_errors
50
+ component_class.validate_initialization_parameters!
51
+ component_class.validate_collection_parameter!
52
+ end
50
53
 
51
54
  templates.each do |template|
52
55
  # Remove existing compiled template methods,
@@ -64,6 +67,8 @@ module ViewComponent
64
67
 
65
68
  define_render_template_for
66
69
 
70
+ component_class._after_compile
71
+
67
72
  CompileCache.register(component_class)
68
73
  end
69
74
 
@@ -163,9 +168,8 @@ module ViewComponent
163
168
  # end
164
169
  #
165
170
  # Without this, `MyOtherComponent` will not look for `my_component/my_other_component.html.erb`
166
- nested_component_files = if component_class.name.include?("::")
167
- nested_component_path = component_class.name.deconstantize.underscore
168
- Dir["#{directory}/#{nested_component_path}/#{component_name}.*{#{extensions}}"]
171
+ nested_component_files = if component_class.name.include?("::") && component_name != filename
172
+ Dir["#{directory}/#{filename}/#{component_name}.*{#{extensions}}"]
169
173
  else
170
174
  []
171
175
  end
@@ -205,7 +209,6 @@ module ViewComponent
205
209
  end
206
210
  end
207
211
 
208
- # :nocov:
209
212
  def compiled_template(file_path)
210
213
  handler = ActionView::Template.handler_for_extension(File.extname(file_path).gsub(".", ""))
211
214
  template = File.read(file_path)
@@ -216,7 +219,6 @@ module ViewComponent
216
219
  handler.call(OpenStruct.new(source: template, identifier: component_class.identifier, type: component_class.type))
217
220
  end
218
221
  end
219
- # :nocov:
220
222
 
221
223
  def call_method_name(variant)
222
224
  if variant.present? && variants.include?(variant)
@@ -22,8 +22,10 @@ module ViewComponent
22
22
  # If there is no slot renderable, we evaluate the block passed to
23
23
  # the slot and return it.
24
24
  def to_s
25
+ return @content if defined?(@content)
26
+
25
27
  view_context = @parent.send(:view_context)
26
- view_context.capture do
28
+ @content = view_context.capture do
27
29
  if defined?(@_component_instance)
28
30
  # render_in is faster than `parent.render`
29
31
  @_component_instance.render_in(view_context, &@_content_block)
@@ -33,6 +35,8 @@ module ViewComponent
33
35
  @_content_block.call
34
36
  end
35
37
  end
38
+
39
+ @content
36
40
  end
37
41
 
38
42
  # Allow access to public component methods via the wrapper
@@ -58,6 +62,10 @@ module ViewComponent
58
62
  @_component_instance.public_send(symbol, *args, &block)
59
63
  end
60
64
 
65
+ def html_safe?
66
+ to_s.html_safe?
67
+ end
68
+
61
69
  def respond_to_missing?(symbol, include_all = false)
62
70
  defined?(@_component_instance) && @_component_instance.respond_to?(symbol, include_all)
63
71
  end
@@ -51,11 +51,17 @@ module ViewComponent
51
51
  if collection
52
52
  class_eval <<-RUBY
53
53
  def #{accessor_name}
54
+ content unless content_evaluated? # ensure content is loaded so slots will be defined
54
55
  #{instance_variable_name} ||= []
55
56
  end
56
57
  RUBY
57
58
  else
58
- attr_reader accessor_name
59
+ class_eval <<-RUBY
60
+ def #{accessor_name}
61
+ content unless content_evaluated? # ensure content is loaded so slots will be defined
62
+ #{instance_variable_name} if defined?(#{instance_variable_name})
63
+ end
64
+ RUBY
59
65
  end
60
66
 
61
67
  # Default class_name to ViewComponent::Slot
@@ -183,6 +183,8 @@ module ViewComponent
183
183
  end
184
184
 
185
185
  def get_slot(slot_name)
186
+ content unless content_evaluated? # ensure content is loaded so slots will be defined
187
+
186
188
  slot = self.class.registered_slots[slot_name]
187
189
  @_set_slots ||= {}
188
190
 
@@ -3,8 +3,8 @@
3
3
  module ViewComponent
4
4
  module VERSION
5
5
  MAJOR = 2
6
- MINOR = 23
7
- PATCH = 1
6
+ MINOR = 26
7
+ PATCH = 0
8
8
 
9
9
  STRING = [MAJOR, MINOR, PATCH].join(".")
10
10
  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.23.1
4
+ version: 2.26.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: 2020-12-10 00:00:00.000000000 Z
11
+ date: 2021-02-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -198,7 +198,7 @@ dependencies:
198
198
  - - "~>"
199
199
  - !ruby/object:Gem::Version
200
200
  version: '0.13'
201
- description:
201
+ description:
202
202
  email:
203
203
  - opensource+view_component@github.com
204
204
  executables: []
@@ -219,6 +219,8 @@ files:
219
219
  - lib/rails/generators/erb/templates/component.html.erb.tt
220
220
  - lib/rails/generators/haml/component_generator.rb
221
221
  - lib/rails/generators/haml/templates/component.html.haml.tt
222
+ - lib/rails/generators/preview/component_generator.rb
223
+ - lib/rails/generators/preview/templates/component_preview.rb.tt
222
224
  - lib/rails/generators/rspec/component_generator.rb
223
225
  - lib/rails/generators/rspec/templates/component_spec.rb.tt
224
226
  - lib/rails/generators/slim/component_generator.rb
@@ -253,7 +255,7 @@ licenses:
253
255
  - MIT
254
256
  metadata:
255
257
  allowed_push_host: https://rubygems.org
256
- post_install_message:
258
+ post_install_message:
257
259
  rdoc_options: []
258
260
  require_paths:
259
261
  - lib
@@ -268,8 +270,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
268
270
  - !ruby/object:Gem::Version
269
271
  version: '0'
270
272
  requirements: []
271
- rubygems_version: 3.1.2
272
- signing_key:
273
+ rubygems_version: 3.0.3
274
+ signing_key:
273
275
  specification_version: 4
274
276
  summary: View components for Rails
275
277
  test_files: []