view_component 2.25.1 → 2.29.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/CHANGELOG.md +69 -0
- data/README.md +0 -8
- data/lib/rails/generators/erb/component_generator.rb +7 -7
- data/lib/rails/generators/haml/component_generator.rb +3 -1
- data/lib/rails/generators/preview/component_generator.rb +5 -1
- data/lib/rails/generators/slim/component_generator.rb +3 -1
- data/lib/view_component.rb +1 -0
- data/lib/view_component/base.rb +86 -7
- data/lib/view_component/collection.rb +0 -1
- data/lib/view_component/compiler.rb +12 -66
- data/lib/view_component/engine.rb +1 -1
- data/lib/view_component/slot_v2.rb +15 -8
- data/lib/view_component/slotable.rb +8 -2
- data/lib/view_component/slotable_v2.rb +9 -1
- data/lib/view_component/test_helpers.rb +15 -1
- data/lib/view_component/translatable.rb +81 -0
- data/lib/view_component/version.rb +4 -2
- metadata +18 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: f8c387807061db7889dbc2e49ccb75a15a2cacd416048a6681b046eb321fb27b
         | 
| 4 | 
            +
              data.tar.gz: fbddc58ff50b3ddaf615f773a681a7a8b7bef3972581dad8b87bf8425efff275
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 63a070fc0a5001b888dbd57a2a9fb0660b351a05b522d9a832efdd2a60f981d218eda2f500a50d01ca6a8c5ada4cccb61f2837e7b956215e89528d9454d3fd39
         | 
| 7 | 
            +
              data.tar.gz: 8176f0427272a8305a520eeaa72ae58be2d9df42282a981f376eb69ac0543017f98d737ecb09f6fa6fc8cacf85adbbf82fbefe337da22432448ab361f35ae447
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -2,6 +2,75 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            ## main
         | 
| 4 4 |  | 
| 5 | 
            +
            ## 2.29.0
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            * Allow Slot lambdas to share data from the parent component and allow chaining on the returned component.
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                *Sjors Baltus, Blake Williams*
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            * Experimental: Add `ViewComponent::Translatable`
         | 
| 12 | 
            +
              * `t` and `translate` now will look first into the sidecar YAML translations file.
         | 
| 13 | 
            +
              * `helpers.t` and `I18n.t` still reference the global Rails translation files.
         | 
| 14 | 
            +
              * `l` and `localize` will still reference the global Rails translation files.
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                *Elia Schito*
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            * Fix rendering output of pass through slots when using HAML.
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                *Alex Robbin, Blake Williams*
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            * Experimental: call `._sidecar_files` to fetch the sidecar files for a given list of extensions, e.g. passing `["yml", "yaml"]`.
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                *Elia Schito*
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            * Fix bug where a single `jbuilder` template matched multiple template handlers.
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                *Niels Slot*
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            ## 2.28.0
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            * Include SlotableV2 by default in Base. **Note:** It's no longer necessary to include `ViewComponent::SlotableV2` to use Slots.
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                *Joel Hawksley*
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            * Prepend Preview routes instead of appending, accounting for cases where host application has catchall route.
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                *Joel Hawksley*
         | 
| 39 | 
            +
             | 
| 40 | 
            +
            * Fix bug where blocks passed to lambda slots will render incorrectly in certain situations.
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                *Blake Williams*
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            ## 2.27.0
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            * Allow customization of the controller used in component tests.
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                *Alex Robbin*
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            * Generate preview at overridden path if one exists when using `--preview` flag.
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                *Nishiki Liu*
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            ## 2.26.1
         | 
| 55 | 
            +
             | 
| 56 | 
            +
            * Fix bug that raises when trying to use a collection before the component has been compiled.
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                *Blake Williams*
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            ## 2.26.0
         | 
| 61 | 
            +
             | 
| 62 | 
            +
            * Lazily evaluate component `content` in `render?`, preventing the `content` block from being evaluated when `render?` returns false.
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                *Blake Williams*
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            * Do not generate template when using `--inline` flag.
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                *Hans Lemuet*
         | 
| 69 | 
            +
             | 
| 70 | 
            +
            * Add `--inline` option to the Haml and Slim generators
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                *Hans Lemuet*
         | 
| 73 | 
            +
             | 
| 5 74 | 
             
            ## 2.25.1
         | 
| 6 75 |  | 
| 7 76 | 
             
            * Experimental: call `._after_compile` class method after a component is compiled.
         | 
    
        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.
         | 
| @@ -10,18 +10,18 @@ module Erb | |
| 10 10 | 
             
                  class_option :inline, type: :boolean, default: false
         | 
| 11 11 |  | 
| 12 12 | 
             
                  def copy_view_file
         | 
| 13 | 
            -
                     | 
| 13 | 
            +
                    unless options["inline"]
         | 
| 14 | 
            +
                      template "component.html.erb", destination
         | 
| 15 | 
            +
                    end
         | 
| 14 16 | 
             
                  end
         | 
| 15 17 |  | 
| 16 18 | 
             
                  private
         | 
| 17 19 |  | 
| 18 20 | 
             
                  def destination
         | 
| 19 | 
            -
                    if  | 
| 20 | 
            -
                       | 
| 21 | 
            -
             | 
| 22 | 
            -
                       | 
| 23 | 
            -
                        File.join("app/components", class_path, "#{file_name}_component.html.erb")
         | 
| 24 | 
            -
                      end
         | 
| 21 | 
            +
                    if options["sidecar"]
         | 
| 22 | 
            +
                      File.join("app/components", class_path, "#{file_name}_component", "#{file_name}_component.html.erb")
         | 
| 23 | 
            +
                    else
         | 
| 24 | 
            +
                      File.join("app/components", class_path, "#{file_name}_component.html.erb")
         | 
| 25 25 | 
             
                    end
         | 
| 26 26 | 
             
                  end
         | 
| 27 27 |  | 
| @@ -8,7 +8,11 @@ module Preview | |
| 8 8 | 
             
                  check_class_collision suffix: "ComponentPreview"
         | 
| 9 9 |  | 
| 10 10 | 
             
                  def create_preview_file
         | 
| 11 | 
            -
                     | 
| 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
         | 
    
        data/lib/view_component.rb
    CHANGED
    
    
    
        data/lib/view_component/base.rb
    CHANGED
    
    | @@ -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 |  | 
| @@ -78,8 +79,8 @@ module ViewComponent | |
| 78 79 | 
             
                  old_current_template = @current_template
         | 
| 79 80 | 
             
                  @current_template = self
         | 
| 80 81 |  | 
| 81 | 
            -
                   | 
| 82 | 
            -
                  @ | 
| 82 | 
            +
                  @_content_evaluated = false
         | 
| 83 | 
            +
                  @_render_in_block = block
         | 
| 83 84 |  | 
| 84 85 | 
             
                  before_render
         | 
| 85 86 |  | 
| @@ -130,7 +131,7 @@ module ViewComponent | |
| 130 131 | 
             
                  @helpers ||= controller.view_context
         | 
| 131 132 | 
             
                end
         | 
| 132 133 |  | 
| 133 | 
            -
                # Exposes . | 
| 134 | 
            +
                # Exposes .virtual_path as an instance method
         | 
| 134 135 | 
             
                def virtual_path
         | 
| 135 136 | 
             
                  self.class.virtual_path
         | 
| 136 137 | 
             
                end
         | 
| @@ -177,11 +178,25 @@ module ViewComponent | |
| 177 178 | 
             
                  @request ||= controller.request
         | 
| 178 179 | 
             
                end
         | 
| 179 180 |  | 
| 180 | 
            -
                attr_reader : | 
| 181 | 
            +
                attr_reader :view_context
         | 
| 182 | 
            +
             | 
| 183 | 
            +
                def content
         | 
| 184 | 
            +
                  return @_content if defined?(@_content)
         | 
| 185 | 
            +
                  @_content_evaluated = true
         | 
| 186 | 
            +
             | 
| 187 | 
            +
                  @_content = if @view_context && @_render_in_block
         | 
| 188 | 
            +
                    view_context.capture(self, &@_render_in_block)
         | 
| 189 | 
            +
                  end
         | 
| 190 | 
            +
                end
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                def content_evaluated?
         | 
| 193 | 
            +
                  @_content_evaluated
         | 
| 194 | 
            +
                end
         | 
| 181 195 |  | 
| 182 196 | 
             
                # The controller used for testing components.
         | 
| 183 | 
            -
                # Defaults to ApplicationController | 
| 184 | 
            -
                #  | 
| 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.
         | 
| 185 200 | 
             
                mattr_accessor :test_controller
         | 
| 186 201 | 
             
                @@test_controller = "ApplicationController"
         | 
| 187 202 |  | 
| @@ -191,6 +206,47 @@ module ViewComponent | |
| 191 206 | 
             
                class << self
         | 
| 192 207 | 
             
                  attr_accessor :source_location, :virtual_path
         | 
| 193 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 | 
            +
             | 
| 194 250 | 
             
                  # Render a component collection.
         | 
| 195 251 | 
             
                  def with_collection(collection, **args)
         | 
| 196 252 | 
             
                    Collection.new(self, collection, **args)
         | 
| @@ -256,7 +312,14 @@ module ViewComponent | |
| 256 312 | 
             
                    if areas.include?(:content)
         | 
| 257 313 | 
             
                      raise ArgumentError.new ":content is a reserved content area name. Please use another name, such as ':body'"
         | 
| 258 314 | 
             
                    end
         | 
| 259 | 
            -
             | 
| 315 | 
            +
             | 
| 316 | 
            +
                    areas.each do |area|
         | 
| 317 | 
            +
                      define_method area.to_sym do
         | 
| 318 | 
            +
                        content unless content_evaluated? # ensure content is loaded so content_areas will be defined
         | 
| 319 | 
            +
                        instance_variable_get(:"@#{area}") if instance_variable_defined?(:"@#{area}")
         | 
| 320 | 
            +
                      end
         | 
| 321 | 
            +
                    end
         | 
| 322 | 
            +
             | 
| 260 323 | 
             
                    self.content_areas = areas
         | 
| 261 324 | 
             
                  end
         | 
| 262 325 |  | 
| @@ -304,6 +367,22 @@ module ViewComponent | |
| 304 367 | 
             
                    )
         | 
| 305 368 | 
             
                  end
         | 
| 306 369 |  | 
| 370 | 
            +
                  def collection_parameter
         | 
| 371 | 
            +
                    if provided_collection_parameter
         | 
| 372 | 
            +
                      provided_collection_parameter
         | 
| 373 | 
            +
                    else
         | 
| 374 | 
            +
                      name && name.demodulize.underscore.chomp("_component").to_sym
         | 
| 375 | 
            +
                    end
         | 
| 376 | 
            +
                  end
         | 
| 377 | 
            +
             | 
| 378 | 
            +
                  def collection_counter_parameter
         | 
| 379 | 
            +
                    "#{collection_parameter}_counter".to_sym
         | 
| 380 | 
            +
                  end
         | 
| 381 | 
            +
             | 
| 382 | 
            +
                  def counter_argument_present?
         | 
| 383 | 
            +
                    instance_method(:initialize).parameters.map(&:second).include?(collection_counter_parameter)
         | 
| 384 | 
            +
                  end
         | 
| 385 | 
            +
             | 
| 307 386 | 
             
                  private
         | 
| 308 387 |  | 
| 309 388 | 
             
                  def initialize_parameter_names
         | 
| @@ -10,7 +10,6 @@ module ViewComponent | |
| 10 10 | 
             
                def render_in(view_context, &block)
         | 
| 11 11 | 
             
                  iterator = ActionView::PartialIteration.new(@collection.size)
         | 
| 12 12 |  | 
| 13 | 
            -
                  component.compile(raise_errors: true)
         | 
| 14 13 | 
             
                  component.validate_collection_parameter!(validate_default: true)
         | 
| 15 14 |  | 
| 16 15 | 
             
                  @collection.map do |item|
         | 
| @@ -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 ||=  | 
| 140 | 
            -
                     | 
| 141 | 
            -
             | 
| 142 | 
            -
                     | 
| 143 | 
            -
                       | 
| 144 | 
            -
                       | 
| 145 | 
            -
             | 
| 146 | 
            -
             | 
| 147 | 
            -
             | 
| 148 | 
            -
             | 
| 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. | 
| 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 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 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,13 +49,19 @@ 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 | 
            +
                            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 | 
            -
                         | 
| 59 | 
            +
                        class_eval <<-RUBY, __FILE__, __LINE__ + 1
         | 
| 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 |  | 
| @@ -224,7 +226,13 @@ module ViewComponent | |
| 224 226 | 
             
                    # Use `bind(self)` to ensure lambda is executed in the context of the
         | 
| 225 227 | 
             
                    # current component. This is necessary to allow the lambda to access helper
         | 
| 226 228 | 
             
                    # methods like `content_tag` as well as parent component state.
         | 
| 227 | 
            -
                    renderable_value =  | 
| 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
         | 
| 228 236 |  | 
| 229 237 | 
             
                    # Function calls can return components, so if it's a component handle it specially
         | 
| 230 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 | 
| 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
         | 
    
        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.29.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 | 
| 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. | 
| 288 | 
            +
            rubygems_version: 3.1.2
         | 
| 274 289 | 
             
            signing_key: 
         | 
| 275 290 | 
             
            specification_version: 4
         | 
| 276 291 | 
             
            summary: View components for Rails
         |