view_component 2.26.0 → 2.30.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5a1fb023bfaa4a1ea26166ddd22b5fcd4a7e5f5da1bc7f10039c2bc557f1a94c
4
- data.tar.gz: 5274b49f0e18f521b15181c70aeb4edcf3458cba13dc8c8ea59b9331d26681c5
3
+ metadata.gz: 6bb3c2781d5f07f404e5bf9582758537704a05c3937529c250ae657541d4f664
4
+ data.tar.gz: 0efa6d2e3bbf03ed206e02b9dad11bea1f5649ab655dfff78d1f48c68414129f
5
5
  SHA512:
6
- metadata.gz: fc60826197c5dbd54ebd53517ff59eab097449913802a4e17df9db33ab54d1daf8ff2e00d1629058962c267274ec7fe6e362aa036206d379e27c7c832db7d05c
7
- data.tar.gz: b3a65f7a829d9e9fc675904d61c6d1c1015c194c5a7abe596961b3cb20a3a4c2045f1ce49b1b98a551c4b745688c38610d3a86040c1f7f0229c3b247a51f9c65
6
+ metadata.gz: 9222381535158cc047e33e37341329c232ac984ee3f6895e95251cd9dabc9b92baebb69b336500932c264b48eb7f74ef0b7d3f918473d8fe83151ad73c18323b
7
+ data.tar.gz: 4da1bd8980affd316288695a52e9fcdccad912f3447081f0186194190ad48e52d397f36c34b3bea70ea743d32f2eda8d37779ecb588b50cbd626f7d89a4f365c
data/CHANGELOG.md CHANGED
@@ -2,6 +2,67 @@
2
2
 
3
3
  ## main
4
4
 
5
+ ## 2.30.0
6
+
7
+ * Deprecate `with_content_areas` in favor of [slots](https://viewcomponent.org/guide/slots.html).
8
+
9
+ *Joel Hawksley*
10
+
11
+ ## 2.29.0
12
+
13
+ * Allow Slot lambdas to share data from the parent component and allow chaining on the returned component.
14
+
15
+ *Sjors Baltus, Blake Williams*
16
+
17
+ * Experimental: Add `ViewComponent::Translatable`
18
+ * `t` and `translate` now will look first into the sidecar YAML translations file.
19
+ * `helpers.t` and `I18n.t` still reference the global Rails translation files.
20
+ * `l` and `localize` will still reference the global Rails translation files.
21
+
22
+ *Elia Schito*
23
+
24
+ * Fix rendering output of pass through slots when using HAML.
25
+
26
+ *Alex Robbin, Blake Williams*
27
+
28
+ * Experimental: call `._sidecar_files` to fetch the sidecar files for a given list of extensions, e.g. passing `["yml", "yaml"]`.
29
+
30
+ *Elia Schito*
31
+
32
+ * Fix bug where a single `jbuilder` template matched multiple template handlers.
33
+
34
+ *Niels Slot*
35
+
36
+ ## 2.28.0
37
+
38
+ * Include SlotableV2 by default in Base. **Note:** It's no longer necessary to include `ViewComponent::SlotableV2` to use Slots.
39
+
40
+ *Joel Hawksley*
41
+
42
+ * Prepend Preview routes instead of appending, accounting for cases where host application has catchall route.
43
+
44
+ *Joel Hawksley*
45
+
46
+ * Fix bug where blocks passed to lambda slots will render incorrectly in certain situations.
47
+
48
+ *Blake Williams*
49
+
50
+ ## 2.27.0
51
+
52
+ * Allow customization of the controller used in component tests.
53
+
54
+ *Alex Robbin*
55
+
56
+ * Generate preview at overridden path if one exists when using `--preview` flag.
57
+
58
+ *Nishiki Liu*
59
+
60
+ ## 2.26.1
61
+
62
+ * Fix bug that raises when trying to use a collection before the component has been compiled.
63
+
64
+ *Blake Williams*
65
+
5
66
  ## 2.26.0
6
67
 
7
68
  * Lazily evaluate component `content` in `render?`, preventing the `content` block from being evaluated when `render?` returns false.
data/README.md CHANGED
@@ -6,14 +6,6 @@ A framework for building reusable, testable & encapsulated view components in Ru
6
6
 
7
7
  See [viewcomponent.org](https://viewcomponent.org/) for documentation.
8
8
 
9
- ## Installation
10
-
11
- In `Gemfile`, add:
12
-
13
- ```ruby
14
- gem "view_component", require: "view_component/engine"
15
- ```
16
-
17
9
  ## Contributing
18
10
 
19
11
  This project is intended to be a safe, welcoming space for collaboration. Contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. We recommend reading the [contributing guide](./CONTRIBUTING.md) as well.
@@ -8,7 +8,11 @@ module Preview
8
8
  check_class_collision suffix: "ComponentPreview"
9
9
 
10
10
  def create_preview_file
11
- template "component_preview.rb", File.join("test/components/previews", class_path, "#{file_name}_component_preview.rb")
11
+ preview_paths = Rails.application.config.view_component.preview_paths
12
+ return if preview_paths.count > 1
13
+
14
+ path_prefix = preview_paths.one? ? preview_paths.first : "test/components/previews"
15
+ template "component_preview.rb", File.join(path_prefix, class_path, "#{file_name}_component_preview.rb")
12
16
  end
13
17
 
14
18
  private
@@ -12,4 +12,5 @@ module ViewComponent
12
12
  autoload :TestHelpers
13
13
  autoload :TestCase
14
14
  autoload :TemplateError
15
+ autoload :Translatable
15
16
  end
@@ -12,6 +12,7 @@ module ViewComponent
12
12
  class Base < ActionView::Base
13
13
  include ActiveSupport::Configurable
14
14
  include ViewComponent::Previewable
15
+ include ViewComponent::SlotableV2
15
16
 
16
17
  ViewContextCalledBeforeRenderError = Class.new(StandardError)
17
18
 
@@ -130,7 +131,7 @@ module ViewComponent
130
131
  @helpers ||= controller.view_context
131
132
  end
132
133
 
133
- # Exposes .virutal_path as an instance method
134
+ # Exposes .virtual_path as an instance method
134
135
  def virtual_path
135
136
  self.class.virtual_path
136
137
  end
@@ -193,8 +194,9 @@ module ViewComponent
193
194
  end
194
195
 
195
196
  # The controller used for testing components.
196
- # Defaults to ApplicationController. This should be set early
197
- # in the initialization process and should be set to a string.
197
+ # Defaults to ApplicationController, but can be configured
198
+ # on a per-test basis using `with_controller_class`.
199
+ # This should be set early in the initialization process and should be a string.
198
200
  mattr_accessor :test_controller
199
201
  @@test_controller = "ApplicationController"
200
202
 
@@ -204,6 +206,47 @@ module ViewComponent
204
206
  class << self
205
207
  attr_accessor :source_location, :virtual_path
206
208
 
209
+ # EXPERIMENTAL: This API is experimental and may be removed at any time.
210
+ # Find sidecar files for the given extensions.
211
+ #
212
+ # The provided array of extensions is expected to contain
213
+ # strings starting without the "dot", example: `["erb", "haml"]`.
214
+ #
215
+ # For example, one might collect sidecar CSS files that need to be compiled.
216
+ def _sidecar_files(extensions)
217
+ return [] unless source_location
218
+
219
+ extensions = extensions.join(",")
220
+
221
+ # view files in a directory named like the component
222
+ directory = File.dirname(source_location)
223
+ filename = File.basename(source_location, ".rb")
224
+ component_name = name.demodulize.underscore
225
+
226
+ # Add support for nested components defined in the same file.
227
+ #
228
+ # e.g.
229
+ #
230
+ # class MyComponent < ViewComponent::Base
231
+ # class MyOtherComponent < ViewComponent::Base
232
+ # end
233
+ # end
234
+ #
235
+ # Without this, `MyOtherComponent` will not look for `my_component/my_other_component.html.erb`
236
+ nested_component_files = if name.include?("::") && component_name != filename
237
+ Dir["#{directory}/#{filename}/#{component_name}.*{#{extensions}}"]
238
+ else
239
+ []
240
+ end
241
+
242
+ # view files in the same directory as the component
243
+ sidecar_files = Dir["#{directory}/#{component_name}.*{#{extensions}}"]
244
+
245
+ sidecar_directory_files = Dir["#{directory}/#{component_name}/#{filename}.*{#{extensions}}"]
246
+
247
+ (sidecar_files - [source_location] + sidecar_directory_files + nested_component_files).uniq
248
+ end
249
+
207
250
  # Render a component collection.
208
251
  def with_collection(collection, **args)
209
252
  Collection.new(self, collection, **args)
@@ -266,6 +309,11 @@ module ViewComponent
266
309
  end
267
310
 
268
311
  def with_content_areas(*areas)
312
+ ActiveSupport::Deprecation.warn(
313
+ "`with_content_areas` is deprecated and will be removed in ViewComponent v3.0.0.\n" \
314
+ "Use slots (https://viewcomponent.org/guide/slots.html) instead."
315
+ )
316
+
269
317
  if areas.include?(:content)
270
318
  raise ArgumentError.new ":content is a reserved content area name. Please use another name, such as ':body'"
271
319
  end
@@ -324,6 +372,22 @@ module ViewComponent
324
372
  )
325
373
  end
326
374
 
375
+ def collection_parameter
376
+ if provided_collection_parameter
377
+ provided_collection_parameter
378
+ else
379
+ name && name.demodulize.underscore.chomp("_component").to_sym
380
+ end
381
+ end
382
+
383
+ def collection_counter_parameter
384
+ "#{collection_parameter}_counter".to_sym
385
+ end
386
+
387
+ def counter_argument_present?
388
+ instance_method(:initialize).parameters.map(&:second).include?(collection_counter_parameter)
389
+ end
390
+
327
391
  private
328
392
 
329
393
  def initialize_parameter_names
@@ -24,28 +24,6 @@ module ViewComponent
24
24
  )
25
25
  end
26
26
 
27
- # Remove any existing singleton methods,
28
- # as Ruby warns when redefining a method.
29
- component_class.remove_possible_singleton_method(:collection_parameter)
30
- component_class.remove_possible_singleton_method(:collection_counter_parameter)
31
- component_class.remove_possible_singleton_method(:counter_argument_present?)
32
-
33
- component_class.define_singleton_method(:collection_parameter) do
34
- if provided_collection_parameter
35
- provided_collection_parameter
36
- else
37
- name.demodulize.underscore.chomp("_component").to_sym
38
- end
39
- end
40
-
41
- component_class.define_singleton_method(:collection_counter_parameter) do
42
- "#{collection_parameter}_counter".to_sym
43
- end
44
-
45
- component_class.define_singleton_method(:counter_argument_present?) do
46
- instance_method(:initialize).parameters.map(&:second).include?(collection_counter_parameter)
47
- end
48
-
49
27
  if raise_errors
50
28
  component_class.validate_initialization_parameters!
51
29
  component_class.validate_collection_parameter!
@@ -83,7 +61,7 @@ module ViewComponent
83
61
  "elsif variant.to_sym == :#{variant}\n #{call_method_name(variant)}"
84
62
  end.join("\n")
85
63
 
86
- component_class.class_eval <<-RUBY
64
+ component_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1
87
65
  def render_template_for(variant = nil)
88
66
  if variant.nil?
89
67
  call
@@ -136,50 +114,18 @@ module ViewComponent
136
114
  end
137
115
 
138
116
  def templates
139
- @templates ||= matching_views_in_source_location.each_with_object([]) do |path, memo|
140
- pieces = File.basename(path).split(".")
141
-
142
- memo << {
143
- path: path,
144
- variant: pieces.second.split("+").second&.to_sym,
145
- handler: pieces.last
146
- }
147
- end
148
- end
149
-
150
- def matching_views_in_source_location
151
- source_location = component_class.source_location
152
- return [] unless source_location
153
-
154
- extensions = ActionView::Template.template_handler_extensions.join(",")
155
-
156
- # view files in a directory named like the component
157
- directory = File.dirname(source_location)
158
- filename = File.basename(source_location, ".rb")
159
- component_name = component_class.name.demodulize.underscore
160
-
161
- # Add support for nested components defined in the same file.
162
- #
163
- # e.g.
164
- #
165
- # class MyComponent < ViewComponent::Base
166
- # class MyOtherComponent < ViewComponent::Base
167
- # end
168
- # end
169
- #
170
- # Without this, `MyOtherComponent` will not look for `my_component/my_other_component.html.erb`
171
- nested_component_files = if component_class.name.include?("::") && component_name != filename
172
- Dir["#{directory}/#{filename}/#{component_name}.*{#{extensions}}"]
173
- else
174
- []
117
+ @templates ||= begin
118
+ extensions = ActionView::Template.template_handler_extensions
119
+
120
+ component_class._sidecar_files(extensions).each_with_object([]) do |path, memo|
121
+ pieces = File.basename(path).split(".")
122
+ memo << {
123
+ path: path,
124
+ variant: pieces.second.split("+").second&.to_sym,
125
+ handler: pieces.last
126
+ }
127
+ end
175
128
  end
176
-
177
- # view files in the same directory as the component
178
- sidecar_files = Dir["#{directory}/#{component_name}.*{#{extensions}}"]
179
-
180
- sidecar_directory_files = Dir["#{directory}/#{component_name}/#{filename}.*{#{extensions}}"]
181
-
182
- (sidecar_files - [source_location] + sidecar_directory_files + nested_component_files)
183
129
  end
184
130
 
185
131
  def inline_calls
@@ -90,7 +90,7 @@ module ViewComponent
90
90
  options = app.config.view_component
91
91
 
92
92
  if options.show_previews
93
- app.routes.append do
93
+ app.routes.prepend do
94
94
  preview_controller = options.preview_controller.sub(/Controller$/, "").underscore
95
95
 
96
96
  get options.preview_route, to: "#{preview_controller}#index", as: :preview_view_components, internal: true
@@ -25,15 +25,22 @@ module ViewComponent
25
25
  return @content if defined?(@content)
26
26
 
27
27
  view_context = @parent.send(:view_context)
28
- @content = view_context.capture do
29
- if defined?(@_component_instance)
30
- # render_in is faster than `parent.render`
31
- @_component_instance.render_in(view_context, &@_content_block)
32
- elsif defined?(@_content)
33
- @_content
34
- elsif defined?(@_content_block)
35
- @_content_block.call
28
+
29
+ @content = if defined?(@_component_instance)
30
+ # render_in is faster than `parent.render`
31
+ if defined?(@_content_block)
32
+ view_context.capture do
33
+ @_component_instance.render_in(view_context, &@_content_block)
34
+ end
35
+ else
36
+ view_context.capture do
37
+ @_component_instance.render_in(view_context)
38
+ end
36
39
  end
40
+ elsif defined?(@_content)
41
+ @_content
42
+ elsif defined?(@_content_block)
43
+ view_context.capture(&@_content_block)
37
44
  end
38
45
 
39
46
  @content
@@ -49,14 +49,14 @@ module ViewComponent
49
49
 
50
50
  # If the slot is a collection, define an accesor that defaults to an empty array
51
51
  if collection
52
- class_eval <<-RUBY
52
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
53
53
  def #{accessor_name}
54
54
  content unless content_evaluated? # ensure content is loaded so slots will be defined
55
55
  #{instance_variable_name} ||= []
56
56
  end
57
57
  RUBY
58
58
  else
59
- class_eval <<-RUBY
59
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
60
60
  def #{accessor_name}
61
61
  content unless content_evaluated? # ensure content is loaded so slots will be defined
62
62
  #{instance_variable_name} if defined?(#{instance_variable_name})
@@ -226,7 +226,13 @@ module ViewComponent
226
226
  # Use `bind(self)` to ensure lambda is executed in the context of the
227
227
  # current component. This is necessary to allow the lambda to access helper
228
228
  # methods like `content_tag` as well as parent component state.
229
- renderable_value = slot_definition[:renderable_function].bind(self).call(*args, **kwargs, &block)
229
+ renderable_value = if block_given?
230
+ slot_definition[:renderable_function].bind(self).call(*args, **kwargs) do |*args, **kwargs|
231
+ view_context.capture(*args, **kwargs, &block)
232
+ end
233
+ else
234
+ slot_definition[:renderable_function].bind(self).call(*args, **kwargs)
235
+ end
230
236
 
231
237
  # Function calls can return components, so if it's a component handle it specially
232
238
  if renderable_value.respond_to?(:render_in)
@@ -35,7 +35,7 @@ module ViewComponent
35
35
  end
36
36
 
37
37
  def controller
38
- @controller ||= Base.test_controller.constantize.new.tap { |c| c.request = request }.extend(Rails.application.routes.url_helpers)
38
+ @controller ||= build_controller(Base.test_controller.constantize)
39
39
  end
40
40
 
41
41
  def request
@@ -47,7 +47,21 @@ module ViewComponent
47
47
 
48
48
  controller.view_context.lookup_context.variants = variant
49
49
  yield
50
+ ensure
50
51
  controller.view_context.lookup_context.variants = old_variants
51
52
  end
53
+
54
+ def with_controller_class(klass)
55
+ old_controller = defined?(@controller) && @controller
56
+
57
+ @controller = build_controller(klass)
58
+ yield
59
+ ensure
60
+ @controller = old_controller
61
+ end
62
+
63
+ def build_controller(klass)
64
+ klass.new.tap { |c| c.request = request }.extend(Rails.application.routes.url_helpers)
65
+ end
52
66
  end
53
67
  end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+ require "i18n"
5
+ require "action_view/helpers/translation_helper"
6
+ require "active_support/concern"
7
+
8
+ module ViewComponent
9
+ module Translatable
10
+ extend ActiveSupport::Concern
11
+
12
+ included do
13
+ class_attribute :i18n_backend, instance_writer: false, instance_predicate: false
14
+ end
15
+
16
+ class_methods do
17
+ def i18n_scope
18
+ @i18n_scope ||= virtual_path.sub(%r{^/}, "").gsub(%r{/_?}, ".")
19
+ end
20
+
21
+ def _after_compile
22
+ super
23
+
24
+ unless CompileCache.compiled? self
25
+ self.i18n_backend = I18nBackend.new(
26
+ i18n_scope: i18n_scope,
27
+ load_paths: _sidecar_files(%w[yml yaml]),
28
+ )
29
+ end
30
+ end
31
+ end
32
+
33
+ class I18nBackend < ::I18n::Backend::Simple
34
+ EMPTY_HASH = {}.freeze
35
+
36
+ def initialize(i18n_scope:, load_paths:)
37
+ @i18n_scope = i18n_scope.split(".")
38
+ @load_paths = load_paths
39
+ end
40
+
41
+ # Ensure the Simple backend won't load paths from ::I18n.load_path
42
+ def load_translations
43
+ super(@load_paths)
44
+ end
45
+
46
+ def scope_data(data)
47
+ @i18n_scope.reverse_each do |part|
48
+ data = { part => data}
49
+ end
50
+ data
51
+ end
52
+
53
+ def store_translations(locale, data, options = EMPTY_HASH)
54
+ super(locale, scope_data(data), options)
55
+ end
56
+ end
57
+
58
+ def translate(key = nil, locale: nil, **options)
59
+ locale ||= ::I18n.locale
60
+
61
+ key = "#{i18n_scope}#{key}" if key.start_with?(".")
62
+
63
+ result = catch(:exception) do
64
+ i18n_backend.translate(locale, key, options)
65
+ end
66
+
67
+ # Fallback to the global translations
68
+ if result.is_a? ::I18n::MissingTranslation
69
+ result = helpers.t(key, locale: locale, **options)
70
+ end
71
+
72
+ result
73
+ end
74
+ alias :t :translate
75
+
76
+ # Exposes .i18n_scope as an instance method
77
+ def i18n_scope
78
+ self.class.i18n_scope
79
+ end
80
+ end
81
+ end
@@ -3,9 +3,11 @@
3
3
  module ViewComponent
4
4
  module VERSION
5
5
  MAJOR = 2
6
- MINOR = 26
6
+ MINOR = 30
7
7
  PATCH = 0
8
8
 
9
9
  STRING = [MAJOR, MINOR, PATCH].join(".")
10
10
  end
11
11
  end
12
+
13
+ puts ViewComponent::VERSION::STRING if __FILE__ == $PROGRAM_NAME
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.26.0
4
+ version: 2.30.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-02-22 00:00:00.000000000 Z
11
+ date: 2021-04-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -128,6 +128,20 @@ dependencies:
128
128
  - - "~>"
129
129
  - !ruby/object:Gem::Version
130
130
  version: '1'
131
+ - !ruby/object:Gem::Dependency
132
+ name: jbuilder
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '2'
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '2'
131
145
  - !ruby/object:Gem::Dependency
132
146
  name: rubocop
133
147
  requirement: !ruby/object:Gem::Requirement
@@ -249,6 +263,7 @@ files:
249
263
  - lib/view_component/template_error.rb
250
264
  - lib/view_component/test_case.rb
251
265
  - lib/view_component/test_helpers.rb
266
+ - lib/view_component/translatable.rb
252
267
  - lib/view_component/version.rb
253
268
  homepage: https://github.com/github/view_component
254
269
  licenses:
@@ -270,7 +285,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
270
285
  - !ruby/object:Gem::Version
271
286
  version: '0'
272
287
  requirements: []
273
- rubygems_version: 3.0.3
288
+ rubygems_version: 3.1.2
274
289
  signing_key:
275
290
  specification_version: 4
276
291
  summary: View components for Rails