view_component-3.8.0/0000755000004100000410000000000014537061632014615 5ustar www-datawww-dataview_component-3.8.0/docs/0000755000004100000410000000000014537061632015545 5ustar www-datawww-dataview_component-3.8.0/docs/CHANGELOG.md0000644000004100000410000020235514537061632017365 0ustar www-datawww-data--- layout: default title: Changelog nav_order: 5 --- # Changelog ## main ## 3.8.0 * Use correct value for the `config.action_dispatch.show_exceptions` config option for edge Rails. *Cameron Dutro* * Remove unsupported versions of Rails & Ruby from CI matrix. *Reegan Viljoen* * Raise error when uncountable slot names are used in `renders_many` *Hugo Chantelauze* *Reegan Viljoen* * Replace usage of `String#ends_with?` with `String#end_with?` to reduce the dependency on ActiveSupport core extensions. *halo* * Don't add ActionDispatch::Static middleware unless `public_file_server.enabled`. *Daniel Gonzalez* *Reegan Viljoen* * Resolve an issue where slots starting with `call` would cause a `NameError` *Blake Williams* * Add `use_helper` API. *Reegan Viljoen* * Fix bug where the `Rails` module wasn't being searched from the root namespace. *Zenéixe* * Fix bug where `#with_request_url`, set the incorrect `request.fullpath`. *Nachiket Pusalkar* * Allow setting method when using the `with_request_url` test helper. *Andrew Duthie* ## 3.7.0 * Support Rails 7.1 in CI. *Reegan Viljoen* *Cameron Dutro* * Document the capture compatibility patch on the Known issues page. *Simon Fish* * Add Simundia to list of companies using ViewComponent. *Alexandre Ignjatovic* * Reduce UnboundMethod objects by memoizing initialize_parameters. *Rainer Borene* * Improve docs about inline templates interpolation. *Hans Lemuet* * Update generators.md to clarify the way of changing `config.view_component.view_component_path`. *Shozo Hatta* * Attempt to fix Ferrum timeout errors by creating driver with unique name. *Cameron Dutro* ## 3.6.0 * Refer to `helpers` in `NameError` message in development and test environments. *Simon Fish* * Fix API documentation and revert unnecessary change in `preview.rb`. *Richard Macklin* * Initialize ViewComponent::Config with defaults before framework load. *Simon Fish* * Add 3.2 to the list of Ruby CI versions *Igor Drozdov* * Stop running PVC's `docs:preview` rake task in CI, as the old docsite has been removed. *Cameron Dutro* * Minor testing documentation improvement. *Travis Gaff* * Add SearchApi to users list. *Sebastjan Prachovskij* * Fix `#with_request_url` to ensure `request.query_parameters` is an instance of ActiveSupport::HashWithIndifferentAccess. *milk1000cc* * Add PeopleForce to list of companies using ViewComponent. *Volodymyr Khandiuk* ## 3.5.0 * Add Skroutz to users list. *Chris Nitsas* * Improve implementation of `#render_parent` so it respects variants and deep inheritance hierarchies. *Cameron Dutro* * Add CharlieHR to users list. *Alex Balhatchet* ## 3.4.0 * Avoid including Rails `url_helpers` into `Preview` class when they're not defined. *Richard Macklin* * Allow instrumentation to be automatically included in Server-Timing headers generated by Rails. To enable this set the config `config.use_deprecated_instrumentation_name = false`. The old key `!render.view_component` is deprecated: update ActiveSupport::Notification subscriptions to `render.view_component`. *Travis Gaff* ## 3.3.0 * Include InlineTemplate by default in Base. **Note:** It's no longer necessary to include `ViewComponent::InlineTemplate` to use inline templates. *Joel Hawksley* * Allow Setting host when using the `with_request_url` test helper. *Daniel Alfaro* * Resolve ambiguous preview paths when using components without the Component suffix. *Reed Law* ## 3.2.0 * Fix viewcomponent.org Axe violations. *Joel Hawksley* * Fix example of RSpec configuration in docs *Pasha Kalashnikov* * Add URL helpers to previews *Reegan Viljoen* ## 3.1.0 * Check `defined?(Rails) && Rails.application` before using `ViewComponent::Base.config.view_component_path`. *Donapieppo* * Allow customization of polymorphic slot setters. *Cameron Dutro* * Fix duplication in configuration docs. *Tom Chen* * Fix helpers not reloading in development. *Jonathan del Strother* * Add `SECURITY.md`. *Joel Hawksley* * Add Ophelos to list of companies using ViewComponent. *Graham Rogers* * Add FlightLogger to list of companies using ViewComponent. *Joseph Carpenter* * Fix coverage reports overwriting each other when running locally. *Jonathan del Strother* * Add @reeganviljoen to triage team. *Reegan Viljoen* ### v3.0.0 1,000+ days and 100+ releases later, the 200+ contributors to ViewComponent are proud to ship v3.0.0! We're so grateful for all the work of community members to get us to this release. Whether it’s filing bug reports, designing APIs in long-winded discussion threads, or writing code itself, ViewComponent is built by the community, for the community. We couldn’t be more proud of what we’re building together :heart: This release makes the following breaking changes, many of which have long been deprecated: * BREAKING: Remove deprecated slots setter methods. Use `with_SLOT_NAME` instead. *Joel Hawksley* * BREAKING: Remove deprecated SlotsV1 in favor of current SlotsV2. *Joel Hawksley* * BREAKING: Remove deprecated `content_areas` feature. Use Slots instead. *Joel Hawksley* * BREAKING: Remove deprecated support for loading ViewComponent engine manually. Make sure `require "view_component/engine"` is removed from `Gemfile`. *Joel Hawksley* * BREAKING: Remove deprecated `generate_*` methods. Use `generate.*` instead. *Joel Hawksley* * BREAKING: Remove deprecated `with_variant` method. *Joel Hawksley* * BREAKING: Remove deprecated `rendered_component` in favor of `rendered_content`. *Joel Hawksley* * BREAKING: Remove deprecated `config.preview_path` in favor of `config.preview_paths`. *Joel Hawksley* * BREAKING: Support Ruby 2.7+ instead of 2.4+ *Joel Hawksley* * BREAKING: Remove deprecated `before_render_check`. *Joel Hawksley* * BREAKING: Change counter variable to start iterating from `0` instead of `1`. *Frank S* * BREAKING: `#SLOT_NAME` getter no longer accepts arguments. This change was missed as part of the earlier deprecation in `3.0.0.rc1`. *Joel Hawksley* * BREAKING: Raise `TranslateCalledBeforeRenderError`, `ControllerCalledBeforeRenderError`, or `HelpersCalledBeforeRenderError` instead of `ViewContextCalledBeforeRenderError`. *Joel Hawksley* * BREAKING: Raise `SlotPredicateNameError`, `RedefinedSlotError`, `ReservedSingularSlotNameError`, `ContentSlotNameError`, `InvalidSlotDefinitionError`, `ReservedPluralSlotNameError`, `ContentAlreadySetForPolymorphicSlotErrror`, `SystemTestControllerOnlyAllowedInTestError`, `SystemTestControllerNefariousPathError`, `NoMatchingTemplatesForPreviewError`, `MultipleMatchingTemplatesForPreviewError`, `DuplicateContentError`, `EmptyOrInvalidInitializerError`, `MissingCollectionArgumentError`, `ReservedParameterError`, `InvalidCollectionArgumentError`, `MultipleInlineTemplatesError`, `MissingPreviewTemplateError`, `DuplicateSlotContentError` or `NilWithContentError` instead of generic error classes. *Joel Hawksley* * BREAKING: Rename `SlotV2` to `Slot` and `SlotableV2` to `Slotable`. *Joel Hawksley* * BREAKING: Incorporate `PolymorphicSlots` into `Slotable`. To migrate, remove any references to `PolymorphicSlots` as they are no longer necessary. *Joel Hawksley* * BREAKING: Rename private TestHelpers#controller, #build_controller, #request, and #preview_class to avoid conflicts. Note: While these methods were undocumented and marked as private, they were accessible in tests. As such, we're considering this to be a breaking change. *Joel Hawksley* * Add support for CSP nonces inside of components. *Reegan Viljoen* ### v3.0.0.rc6 Run into an issue with this release candidate? [Let us know](https://github.com/ViewComponent/view_component/issues/1629). We hope to release v3.0.0 in the near future! * BREAKING: `#SLOT_NAME` getter no longer accepts arguments. This change was missed as part of the earlier deprecation in `3.0.0.rc1`. *Joel Hawksley* * BREAKING: Raise `TranslateCalledBeforeRenderError`, `ControllerCalledBeforeRenderError`, or `HelpersCalledBeforeRenderError` instead of `ViewContextCalledBeforeRenderError`. *Joel Hawksley* * BREAKING: Raise `SlotPredicateNameError`, `RedefinedSlotError`, `ReservedSingularSlotNameError`, `ContentSlotNameError`, `InvalidSlotDefinitionError`, `ReservedPluralSlotNameError`, `ContentAlreadySetForPolymorphicSlotErrror`, `SystemTestControllerOnlyAllowedInTestError`, `SystemTestControllerNefariousPathError`, `NoMatchingTemplatesForPreviewError`, `MultipleMatchingTemplatesForPreviewError`, `DuplicateContentError`, `EmptyOrInvalidInitializerError`, `MissingCollectionArgumentError`, `ReservedParameterError`, `InvalidCollectionArgumentError`, `MultipleInlineTemplatesError`, `MissingPreviewTemplateError`, `DuplicateSlotContentError` or `NilWithContentError` instead of generic error classes. *Joel Hawksley* * Fix bug where `content?` and `with_content` didn't work reliably with slots. *Derek Kniffin, Joel Hawksley* * Add `with_SLOT_NAME_content` helper. *Will Cosgrove* * Allow ActiveRecord objects to be passed to `renders_many`. *Leigh Halliday* * Fix broken links in documentation. *Ellen Keal* * Run `standardrb` against markdown in docs. *Joel Hawksley* * Allow `.with_content` to be redefined by components. *Joel Hawksley* * Run `standardrb` against markdown in docs. *Joel Hawksley* * Raise error if translations are used in initializer. *Joel Hawksley* ## v3.0.0.rc5 Run into an issue with this release candidate? [Let us know](https://github.com/ViewComponent/view_component/issues/1629). * Fix bug where `mkdir_p` failed due to incorrect permissions. *Joel Hawksley* * Check for inline `erb_template` calls when deciding whether to compile a component's superclass. *Justin Kenyon* * Protect against `SystemStackError` if `CaptureCompatibility` module is included more than once. *Cameron Dutro* ## v3.0.0.rc4 Run into an issue with this release candidate? [Let us know](https://github.com/ViewComponent/view_component/issues/1629). * Add `TestHelpers#vc_test_request`. *Joel Hawksley* ## v3.0.0.rc3 Run into an issue with this release candidate? [Let us know](https://github.com/ViewComponent/view_component/issues/1629). * Fix typos in generator docs. *Sascha Karnatz* * Add `TestHelpers#vc_test_controller`. *Joel Hawksley* * Document `config.view_component.capture_compatibility_patch_enabled` as option for the known incompatibilities with Rails form helpers. *Tobias L. Maier* * Add support for experimental inline templates. *Blake Williams* * Expose `translate` and `t` I18n methods on component classes. *Elia Schito* * Protect against Arbitrary File Read edge case in `ViewComponentsSystemTestController`. *Nick Malcolm* ## v3.0.0.rc2 Run into an issue with this release? [Let us know](https://github.com/ViewComponent/view_component/issues/1629). * BREAKING: Rename `SlotV2` to `Slot` and `SlotableV2` to `Slotable`. *Joel Hawksley* * BREAKING: Incorporate `PolymorphicSlots` into `Slotable`. To migrate, remove any references to `PolymorphicSlots` as they are no longer necessary. *Joel Hawksley* * BREAKING: Rename private TestHelpers#controller, #build_controller, #request, and #preview_class to avoid conflicts. Note: While these methods were undocumented and marked as private, they were accessible in tests. As such, we're considering this to be a breaking change. *Joel Hawksley* * Avoid loading ActionView::Base during Rails initialization. Originally submitted in #1528. *Jonathan del Strother* * Improve documentation of known incompatibilities with Rails form helpers. *Tobias L. Maier* * Remove dependency on environment task from `view_component:statsetup`. *Svetlin Simonyan* * Add experimental `config.view_component.capture_compatibility_patch_enabled` option resolving rendering issues related to forms, capture, turbo frames, etc. *Blake Williams* * Add `#content?` method that indicates if content has been passed to component. *Joel Hawksley* * Added example of a custom preview controller. *Graham Rogers* * Add Krystal to list of companies using ViewComponent. *Matt Bearman* * Add Mon Ami to list of companies using ViewComponent. *Ethan Lee-Tyson* ## 3.0.0.rc1 1,000+ days and 100+ releases later, the 200+ contributors to ViewComponent are proud to ship v3.0.0! We're so grateful for all the work of community members to get us to this release. Whether it’s filing bug reports, designing APIs in long-winded discussion threads, or writing code itself, ViewComponent is built by the community, for the community. We couldn’t be more proud of what we’re building together :heart: This release makes the following breaking changes, many of which have long been deprecated: * BREAKING: Remove deprecated slots setter methods. Use `with_SLOT_NAME` instead. *Joel Hawksley* * BREAKING: Remove deprecated SlotsV1 in favor of current SlotsV2. *Joel Hawksley* * BREAKING: Remove deprecated `content_areas` feature. Use Slots instead. *Joel Hawksley* * BREAKING: Remove deprecated support for loading ViewComponent engine manually. Make sure `require "view_component/engine"` is removed from `Gemfile`. *Joel Hawksley* * BREAKING: Remove deprecated `generate_*` methods. Use `generate.*` instead. *Joel Hawksley* * BREAKING: Remove deprecated `with_variant` method. *Joel Hawksley* * BREAKING: Remove deprecated `rendered_component` in favor of `rendered_content`. *Joel Hawksley* * BREAKING: Remove deprecated `config.preview_path` in favor of `config.preview_paths`. *Joel Hawksley* * BREAKING: Support Ruby 2.7+ instead of 2.4+ *Joel Hawksley* * BREAKING: Remove deprecated `before_render_check`. *Joel Hawksley* * BREAKING: Change counter variable to start iterating from `0` instead of `1`. *Frank S* Run into an issue with this release? [Let us know](https://github.com/ViewComponent/view_component/issues/1629). ## 2.82.0 * Revert "Avoid loading ActionView::Base during initialization (#1528)" *Jon Rohan* * Fix tests using `with_rendered_component_path` with custom layouts. *Ian Hollander* ## 2.81.0 * Adjust the way response objects are set on the preview controller to work around a recent change in Rails main. *Cameron Dutro* * Fix typo in "Generate a Stimulus controller" documentation. *Ben Trewern* * Modify the `render_in_view_context` test helper to forward its args to the block. *Cameron Dutro* ## 2.80.0 * Move system test endpoint out of the unrelated previews controller. *Edwin Mak* * Display Ruby 2.7 deprecation notice only once, when starting the application. *Henrik Hauge Bjørnskov* * Require Rails 5.2+ in gemspec and update documentation. *Drew Bragg* * Add documentation for using `with_rendered_component_path` with RSpec. *Edwin Mak* ## 2.79.0 * Add ability to pass explicit `preview_path` to preview generator. *Erinna Chen* * Add `with_rendered_component_path` helper for writing component system tests. *Edwin Mak* * Include gem name and deprecation horizon in every deprecation message. *Jan Klimo* ## 2.78.0 * Support variants with dots in their names. *Javi Martín* ## 2.77.0 * Support variants with dashes in their names. *Javi Martín* ## 2.76.0 * `Component.with_collection` supports components that accept splatted keyword arguments. *Zee Spencer* * Remove `config.view_component.use_consistent_rendering_lifecycle` since it is no longer planned for 3.0. *Blake Williams* * Prevent polymorphic slots from calculating `content` when setting a slot. *Blake Williams* * Add ability to pass in the preview class to `render_preview`. *Jon Rohan* * Fix issue causing PVC tests to fail in CI. *Cameron Dutro* * Fix YARD docs build task. *Hans Lemuet* * Add Startup Jobs to list of companies using ViewComponent. *Marc Köhlbrugge* * Run PVC's accessibility tests in a single process to avoid resource contention in CI. *Cameron Dutro* ## 2.75.0 * Avoid loading ActionView::Base during Rails initialization. *Jonathan del Strother* * Mention lambda slots rendering returned values lazily in the guide. *Graham Rogers* * Add "ViewComponent In The Wild" articles to resources. *Alexander Baygeldin* ## 2.74.1 * Add more users of ViewComponent to docs. *Joel Hawksley* * Add a known issue for usage with `turbo_frame_tag` to the documentation. *Vlad Radulescu* * Add note about system testing components with previews. *Joel Hawksley* * Remove locking mechanisms from the compiler. *Cameron Dutro* ## 2.74.0 * Add Avo to list of companies using ViewComponent. *Adrian Marin* * Promote experimental `_output_postamble` method to public API as `output_postamble`. *Joel Hawksley* * Promote experimental `_sidecar_files` method to public API as `sidecar_files`. *Joel Hawksley* * Fix `show_previews` regression introduced in 2.73.0. *Andy Baranov* * `with_request_url` test helper supports router constraints (such as Devise). *Aotokitsuruya* ## 2.73.0 * Remove experimental `_after_compile` lifecycle method. *Joel Hawksley* * Fix capitalization of JavaScript in docs. *Erinna Chen* * Add PrintReleaf to list of companies using ViewComponent. *Ry Kulp* * Simplify CI configuration to a single build per Ruby/Rails version. *Joel Hawksley* * Correctly document `generate.sidecar` config option. *Ruben Smit* * Add Yobbers to list of companies using ViewComponent. *Anton Prins* ## 2.72.0 * Deprecate support for Ruby < 2.7 for removal in v3.0.0. *Joel Hawksley* * Add `changelog_uri` to gemspec. *Joel Hawksley* * Link to `CHANGELOG.md` instead of symlink. *Joel Hawksley. * Add Aluuno to list of companies using ViewComponent. *Daniel Naves de Carvalho* * Add `source_code_uri` to gemspec. *Yoshiyuki Hirano* * Update link to benchmark script in docs. *Daniel Diekmeier* * Add special exception message for `renders_one :content` explaining that content passed as a block will be assigned to the `content` accessor without having to create an explicit slot. *Daniel Diekmeier* ## 2.71.0 **ViewComponent has moved to a new organization: [https://github.com/viewcomponent/view_component](https://github.com/viewcomponent/view_component). See [https://github.com/viewcomponent/view_component/issues/1424](https://github.com/viewcomponent/view_component/issues/1424) for more details.** ## 2.70.0 * `render_preview` can pass parameters to preview. *Joel Hawksley* * Fix docs typos. *Joel Hawksley* * Add architectural decisions to documentation and rename sidebar sections. *Joel Hawksley* * Clarify documentation on testability of Rails views. *Joel Hawksley* * Add Arrows to list of companies using ViewComponent. *Matt Swanson* * Add WIP to list of companies using ViewComponent. *Marc Köhlbrugge* * Update slots documentation to include how to reference slots. *Brittany Ellich* * Add Clio to list of companies using ViewComponent. *Mike Buckley* ## 2.69.0 * Add missing `require` to fix `pvc` build. *Joel Hawksley* * Add `config.view_component.use_consistent_rendering_lifecycle` to ensure side-effects in `content` are consistently evaluated before components are rendered. This change effectively means that `content` is evaluated for every component render where `render?` returns true. As a result, code that's passed to a component via a block/content will now always be evaluated, before `#call`, which can reveal bugs in existing components. This configuration option defaults to `false` but will be enabled in 3.0 and the old behavior will be removed. *Blake Williams* * Update Prism to version 1.28.0. *Thomas Hutterer* * Corrects the deprecation warning for named slots to show the file and line where the slot is called. *River Bailey* ## 2.68.0 * Update `gemspec` author to be ViewComponent team. *Joel Hawksley* * Fix bug where `ViewComponent::Compiler` wasn't required. *Joel Hawksley* ## 2.67.0 * Use ViewComponent::Base.config as the internal endpoint for config. *Simon Fish* * Fix bug where `#with_request_url`, when used with query string, set the incorrect `request.path` and `request.fullpath`. *Franz Liedke* * Add link to [ViewComponentAttributes](https://github.com/amba-Health/view_component_attributes) in Resources section of docs. *Romaric Pascal* * `render_preview` test helper is available by default. It is no longer necessary to include `ViewComponent::RenderPreviewHelper`. *Joel Hawksley* ## 2.66.0 * Add missing `generate.sidecar`, `generate.stimulus_controller`, `generate.locale`, `generate.distinct_locale_files`, `generate.preview` config options to `config.view_component`. *Simon Fish* ## 2.65.0 * Raise `ArgumentError` when conflicting Slots are defined. Before this change it was possible to define Slots with conflicting names, for example: ```ruby class MyComponent < ViewComponent::Base renders_one :item renders_many :items end ``` *Joel Hawksley* ## 2.64.0 * Add `warn_on_deprecated_slot_setter` flag to opt-in to deprecation warning. In [v2.54.0](https://viewcomponent.org/CHANGELOG.html#2540), the Slots API was updated to require the `with_*` prefix for setting Slots. The non-`with_*` setters will be deprecated in a coming version and removed in `v3.0`. To enable the coming deprecation warning, add `warn_on_deprecated_slot_setter`: ```ruby class DeprecatedSlotsSetterComponent < ViewComponent::Base warn_on_deprecated_slot_setter end ``` *Joel Hawksley* * Add [`m`](https://rubygems.org/gems/m) to development environment. *Joel Hawksley* * Fix potential deadlock scenario in the compiler's development mode. *Blake Williams* ## 2.63.0 * Fixed typo in `renders_many` documentation. *Graham Rogers* * Add documentation about working with `turbo-rails`. *Matheus Poli Camilo* * Fix issue causing helper methods to not be available in nested components when the render monkey patch is disabled and `render_component` is used. *Daniel Scheffknecht* ## 2.62.0 * Remove the experimental global output buffer feature. * Restore functionality that used to attempt to compile templates on each call to `#render_in`. * Un-pin `rails` `main` dependency. *Cameron Dutro* * Add blank space between "in" and "ViewComponent" in a deprecation warning. *Vikram Dighe* * Add HappyCo to list of companies using ViewComponent. *Josh Clayton* * Add predicate method support to polymorphic slots. *Graham Rogers* ## 2.61.1 * Revert `Expose Capybara DSL methods directly inside tests.` This change unintentionally broke other Capybara methods and thus introduced a regression. We aren't confident that we can fail forward so we have decided to revert this change. *Joel Hawksley, Blake Williams* * Revert change making content evaluation consistent. *Blake Williams* * Pin `rails` `main` dependency due to incompatibility with Global Output Buffer. *Joel Hawksley* ## 2.61.0 * Ensure side-effects in `content` are consistently evaluated before components are rendered. This change effectively means that `content` is evaluated for every component render where `render?` returns true. As a result, code that is passed to a component via a block/content will now always be evaluated, before `#call`, which can reveal bugs in existing components. *Blake Williams* ## 2.60.0 * Add support for `render_preview` in RSpec tests. *Thomas Hutterer* ## 2.59.0 * Expose Capybara DSL methods directly inside tests. The following Capybara methods are now available directly without having to use the `page` method: * [`all`](https://rubydoc.info/github/teamcapybara/capybara/Capybara%2FNode%2FFinders:all) * [`first`](https://rubydoc.info/github/teamcapybara/capybara/Capybara%2FNode%2FFinders:first) * [`text`](https://rubydoc.info/github/teamcapybara/capybara/Capybara%2FNode%2FSimple:text) * [`find`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FFinders:find) * [`find_all`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FFinders:find_all) * [`find_button`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FFinders:find_button) * [`find_by_id`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FFinders:find_by_id) * [`find_field`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FFinders:find_field) * [`find_link`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FFinders:find_link) * [`has_content?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_content%3F) * [`has_text?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_text%3F) * [`has_css?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_css%3F) * [`has_no_content?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_no_content%3F) * [`has_no_text?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_no_text%3F) * [`has_no_css?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_no_css%3F) * [`has_no_xpath?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_no_xpath%3F) * [`has_xpath?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_xpath%3F) * [`has_link?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_link%3F) * [`has_no_link?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_no_link%3F) * [`has_button?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_button%3F) * [`has_no_button?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_no_button%3F) * [`has_field?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_field%3F) * [`has_no_field?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_no_field%3F) * [`has_checked_field?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_checked_field%3F) * [`has_unchecked_field?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_unchecked_field%3F) * [`has_no_table?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_no_table%3F) * [`has_table?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_table%3F) * [`has_select?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_select%3F) * [`has_no_select?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_no_select%3F) * [`has_selector?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_selector%3F) * [`has_no_selector?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_no_selector%3F) * [`has_no_checked_field?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_no_checked_field%3F) * [`has_no_unchecked_field?`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FNode%2FMatchers:has_no_unchecked_field%3F) * Add support for `within*` Capybara DLS methods: * [`within`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FSession:within) * [`within_element`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FSession:within) * [`within_fieldset`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FSession:within_fieldset) * [`within_table`](https://rubydoc.info/github/teamcapybara/capybara/master/Capybara%2FSession:within_table) *Jacob Carlborg* ## 2.58.0 * Switch to `standardrb`. *Joel Hawksley* * Add BootrAils article to resources. *Joel Hawksley* * Add @boardfish and @spone as maintainers. *Joel Hawksley, Cameron Dutro, Blake Williams* * Re-compile updated, inherited templates when class caching is disabled. *Patrick Arnett* * Add the latest version to the docs index. * Improve the docs: add the versions various features were introduced in. *Hans Lemuet* * Update docs to reflect lack of block content support in controllers. *Joel Hawksley* * Prevent adding duplicates to `autoload_paths`. *Thomas Hutterer* * Add FreeAgent to list of companies using ViewComponent. *Simon Fish* * Include polymorphic slots in `ViewComponent::Base` by default. *Cameron Dutro* * Add per-component config option for stripping newlines from templates before compilation. *Cameron Dutro* * Add link to article by Matouš Borák. *Joel Hawksley* ## 2.57.1 * Fix issue causing `NoMethodError`s when calling helper methods from components rendered as part of a collection. * Fix syntax error in the ERB example in the polymorphic slots docs. *Cameron Dutro* ## 2.57.0 * Add missing `require` for `Translatable` module in `Base`. *Hans Lemuet* * Allow anything that responds to `#render_in` to be rendered in the parent component's view context. *Cameron Dutro* * Fix script/release so it honors semver. *Cameron Dutro* ## 2.56.2 * Restore removed `rendered_component`, marking it for deprecation in v3.0.0. *Tyson Gach, Richard Macklin, Joel Hawksley* ## 2.56.1 * Rename private accessor `rendered_component` to `rendered_content`. *Yoshiyuki Hirano, Simon Dawson* ## 2.56.0 * Introduce experimental `render_preview` test helper. Note: `@rendered_component` in `TestHelpers` has been renamed to `@rendered_content`. *Joel Hawksley* * Move framework tests into sandbox application. *Joel Hawksley* * Add G2 to list of companies that use ViewComponent. *Jack Shuff* * Add Within3 to list of companies that use ViewComponent. *Drew Bragg* * Add Mission Met to list of companies that use ViewComponent. *Nick Smith* * Fix `#with_request_url` test helper not parsing nested query parameters into nested hashes. *Richard Marbach* ## 2.55.0 * Add `render_parent` convenience method to avoid confusion between `<%= super %>` and `<% super %>` in template code. *Cameron Dutro* * Add note about discouraging inheritance. *Joel Hawksley* * Clean up grammar in documentation. *Joel Hawksley* * The ViewComponent team at GitHub is hiring! We're looking for a Rails engineer with accessibility experience: [https://boards.greenhouse.io/github/jobs/4020166](https://boards.greenhouse.io/github/jobs/4020166). Reach out to joelhawksley@github.com with any questions! * The ViewComponent team is hosting a happy hour at RailsConf. Join us for snacks, drinks, and stickers: [https://www.eventbrite.com/e/viewcomponent-happy-hour-tickets-304168585427](https://www.eventbrite.com/e/viewcomponent-happy-hour-tickets-304168585427) ## 2.54.1 * Update docs dependencies. *Joel Hawksley* * Resolve warning in slots API. * Raise in the test environment when ViewComponent code emits a warning. *Blake Williams* ## 2.54.0 * Add `with_*` slot API for defining slots. Note: we plan to deprecate the non `with_*` API for slots in an upcoming release. *Blake Williams* * Add QuickNode to list of companies that use ViewComponent. *Luc Castera* * Include the `Translatable` module by default. *Elia Schito* * Update docs dependencies. *Joel Hawksley* ## 2.53.0 * Add support for relative I18n scopes to translations. *Elia Schito* * Update CI configuration to use latest Rails 7.0. *Hans Lemuet* * Document how to use blocks with lambda slots. *Sam Partington* * Skip Rails 5.2 in local test environment if using incompatible Ruby version. *Cameron Dutro, Blake Williams, Joel Hawksley* * Improve landing page documentation. *Jason Swett* * Add Bearer to list of companies that use ViewComponent. *Yaroslav Shmarov* * Add articles to resources page. *Joel Hawksley* * Enable rendering arbitrary block contents in the view context in tests. *Cameron Dutro* ## 2.52.0 * Add ADR for separate slot getter/setter API. *Blake Williams* * Add the option to use a "global" output buffer so `form_for` and friends can be used with view components. *Cameron Dutro, Blake Williams* * Fix fragment caching in partials when global output buffer is enabled. * Fix template inheritance when eager loading is disabled. *Cameron Dutro* ## 2.51.0 * Update the docs only when releasing a new version. *Hans Lemuet* * Alphabetize companies using ViewComponent and add Brightline to the list. *Jack Schuss* * Add CMYK value for ViewComponent Red color on logo page. *Dylan Smith* * Improve performance by moving template compilation from `#render_in` to `#render_template_for`. *Cameron Dutro* ## 2.50.0 * Add tests for `layout` usage when rendering via controller. *Felipe Sateler* * Support returning Arrays from i18n files, and support marking them as HTML-safe translations. *foca* * Add Cometeer and Framework to users list. *Elia Schito* * Update Microsoft Vale styles. *Simon Fish* * Fix example in testing guide for how to setup default Rails tests. *Steven Hansen* * Update benchmark script to render multiple components/partials instead of a single instance per-run. *Blake Williams* * Add predicate methods `#{slot_name}?` to slots. *Hans Lemuet* * Use a dedicated deprecation instance, silence it while testing. *Max Beizer, Hans Lemuet, Elia Schito* * Fix Ruby warnings. *Hans Lemuet* * Place all generator options under `config.generate` namespace. *Simon Fish* * Allow preview generator to use provided component attributes. * Add config option `config.view_component.generate.preview` to enable project-wide preview generation. * Ensure all generated `.rb` files include `# frozen_string_literal: true` statement. *Bob Maerten* * Add Shogun to users list. *Bernie Chiu* ## 2.49.1 * Patch XSS vulnerability in `ViewComponent::Translatable` module caused by improperly escaped interpolation arguments. *Cameron Dutro* ## 2.49.0 * Fix path handling for evaluated view components that broke in Ruby 3.1. *Adam Hess* * Fix support for the `default:` option for a global translation. *Elia Schito* * Ensure i18n scope is a symbol to protect lookups. *Simon Fish* * Small update to preview docs to include rspec mention. *Leigh Halliday* * Small improvements to collection iteration docs. *Brian O'Rourke* * Add good and bad examples to `ViewComponents in practice`. *Joel Hawksley* * Add Ruby 3.1 and Rails 7.0 to CI. *Peter Goldstein* * Move preview logic to module for easier app integration. *Sammy Henningsson* ## 2.48.0 * Correct path in example test command in Contributing docs. *Mark Wilkinson* * Update link to GOV.UK Components library in the resources list. *Peter Yates* * Add Lookbook to Resources docs page. *Mark Perkins* * Add blocking compiler mode for use in Rails development and testing modes, improving thread safety. *Horia Radu* * Add generators to support `tailwindcss-rails`. *Dino Maric, Hans Lemuet* * Add a namespaced component example to docs. *Hans Lemuet* * Setup `Appraisal` to add flexibility when testing ViewComponent against multiple Rails versions. *Hans Lemuet* * Return correct values for `request.path` and `request.query_string` methods when using the `with_request_url` test helper. *Vasiliy Matyushin* * Improve style in generators docs. *Hans Lemuet* * Correctly type Ruby version strings and update Rails versions used in CI configuration. *Hans Lemuet* * Make `ViewComponent::Collection` act like a collection of view components. *Sammy Henningsson* * Update `@param` of `#render_inline` to include `ViewComponent::Collection`. *Yutaka Kamei* * Add Wecasa to users list. *Mohamed Ziata* ## 2.47.0 * Display preview source on previews that exclusively use templates. *Edwin Mak* * Add a test to ensure trailing newlines are stripped when rendering with `#render_in`. *Simon Fish* * Add WEBrick as a depenency to the application. *Connor McQuillan* * Update Ruby version in `.tool-versions`. *Connor McQuillan* * Add a test to ensure blocks can be passed into lambda slots without the need for any other arguments. *Simon Fish* * Add linters for file consistency. *Simon Fish* * Add @boardfish to docs/index.md and sort contributors. *Simon Fish* * Set up Codespaces for bug replication. *Simon Fish* * Add instructions for replicating bugs and failures. *Simon Fish* * Make @boardfish a committer. *Joel Hawksley* * Validate collection parameter with Active Model attribute names. *Simon Fish* * Fix `helpers` not working with component slots when rendered more than 2 component levels deep. *Blake Williams* * Update ruby to the latest versions *Pedro Paiva* * Fix `vale` linter config options. *Hans Lemuet* * Improve Contributing docs to include how to run tests for a specific version on Rails. *Hans Lemuet* * Add failing test for default form builder and documentation around known issue. *Simon Fish* * Add `--locale` flag to the component generator. Generates as many locale files as defined in `I18n.available_locales`, alongside the component. * Add config option `config.view_component.generate_locale` to enable project-wide locale generation. * Add config option `config.view_component.generate_distinct_locale_files` to enable project-wide per-locale translations file generation. *Bob Maerten* * Add config option `config.view_component.generate_sidecar` to always generate in the sidecar directory. *Gleydson Tavares* ## 2.46.0 * Add thread safety to the compiler. *Horia Radu* * Add theme-specific logo images to readme. *Dylan Smith* * Add Orbit to users list. *Nicolas Goutay* * Increase clarity around purpose and use of slots. *Simon Fish* * Deprecate loading `view_component/engine` directly. **Upgrade notice**: You should update your `Gemfile` like this: ```diff - gem "view_component", require: "view_component/engine"` + gem "view_component" ``` *Yoshiyuki Hirano* ## 2.45.0 * Remove internal APIs from API documentation, fix link to license. *Joel Hawksley* * Add @yhirano55 to triage team. *Joel Hawksley* * Correct a typo in the sample slots code. *Simon Fish* * Add note about `allowed_queries`. *Joel Hawksley* * Add `vale` content linter. *Joel Hawksley* * Remove `require "rails/generators/test_case"` in generator tests. *Yoshiyuki Hirano* * Suppress zeitwerk warning about circular require. *Yoshiyuki Hirano* * Move `test_unit_generator_test.rb` from `test/view_component/` to `test/generators/`. *Yoshiyuki Hirano* * Unify test code of `TestUnitGeneratorTest` with the other generators tests. *Yoshiyuki Hirano* ## 2.44.0 * Rename internal accessor to use private naming. *Joel Hawksley, Blake Williams, Cameron Dutro* * Add Github repo link to docs website header. *Hans Lemuet* * Change logo in README for dark theme readability. *Dylan Smith* * Add Litmus to users list. *Dylan Smith* * Add @dylanatsmith as codeowner of the ViewComponent logo and member of committers team. *Joel Hawksley* * Autoload `CompileCache`, which is optionally called in `engine.rb`. *Gregory Igelmund* * Move frequently asked questions to other pages, add History page. *Joel Hawksley* * Fix typo. *James Hart* * Add `require "method_source"` if it options.show_previews_source is enabled. *Yoshiyuki Hirano* * Move show_previews_source definition to Previewable. *Yoshiyuki Hirano* * Clear cache in MethodSource to apply the change odf preview code without app server restart. *Yoshiyuki Hirano* ## 2.43.1 * Remove unnecessary call to `ruby2_keywords` for polymorphic slot getters. *Cameron Dutro* ## 2.43.0 * Add note about tests and instance methods. *Joel Hawksley* * Flesh out `ViewComponents in practice`. *Joel Hawksley* * Add CODEOWNERS entries for feature areas owned by community committers. *Joel Hawksley* * Separate lint and CI workflows. *Blake Williams* * Add support for `image_path` helper in previews. *Tobias Ahlin, Joel Hawksley* * Add section to docs listing users of ViewComponent. Please submit a PR to add your team to the list! *Joel Hawksley* * Fix loading issue with Stimulus generator and add specs for Stimulus generator. *Peter Sumskas* * Remove dependency on `ActionDispatch::Static` in Rails middleware stack when enabling statics assets for source code preview. *Gregory Igelmund* * Require `view_component/engine` automatically. *Cameron Dutro* ## 2.42.0 * Add logo files and page to docs. *Dylan Smith* * Add `ViewComponents in practice` documentation. *Joel Hawksley* * Fix bug where calling lambda slots without arguments would break in Ruby < 2.7. *Manuel Puyol* * Improve Stimulus controller template to import from `stimulus` or `@hotwired/stimulus`. *Mario Schüttel* * Fix bug where `helpers` would instantiate and use a new `view_context` in each component. *Blake Williams, Ian C. Anderson* * Implement polymorphic slots as experimental feature. See the Slots documentation to learn more. *Cameron Dutro* ## 2.41.0 * Add `sprockets-rails` development dependency to fix test suite failures when using rails@main. *Blake Williams* * Fix Ruby indentation warning. *Blake Williams* * Add `--parent` generator option to specify the parent class. * Add config option `config.view_component.component_parent_class` to change it project-wide. *Hans Lemuet* * Update docs to add example for using Devise helpers in tests. *Matthew Rider* * Fix bug where `with_collection_parameter` didn't inherit from parent component. *Will Drexler, Christian Campoli* * Allow query parameters in `with_request_url` test helper. *Javi Martín* * Add "how to render a component to a string" to FAQ. *Hans Lemuet* * Add `#render_in` to API docs. *Hans Lemuet* * Forward keyword arguments from slot wrapper to component instance using ruby2_keywords. *Cameron Dutro* ## 2.40.0 * Replace antipatterns section in the documentation with best practices. *Blake Williams* * Add components to `rails stats` task. *Nicolas Brousse* * Fix bug when using Slim and writing a slot whose block evaluates to `nil`. *Yousuf Jukaku* * Add documentation for test helpers. *Joel Hawksley* ## 2.39.0 * Clarify documentation of `with_variant` as an override of Action Pack. *Blake Williams, Cameron Dutro, Joel Hawksley* * Update docs page to be called Javascript and CSS, rename Building ViewComponents to Guide. *Joel Hawksley* * Deprecate `Base#with_variant`. *Cameron Dutro* * Error out in the CI if docs/api.md has to be regenerated. *Dany Marcoux* ## 2.38.0 * Add `--stimulus` flag to the component generator. Generates a Stimulus controller alongside the component. * Add config option `config.view_component.generate_stimulus_controller` to always generate a Stimulus controller. *Sebastien Auriault* ## 2.37.0 * Clarify slots example in docs to reduce naming confusion. *Joel Hawksley, Blake Williams* * Fix error in documentation for `render_many` passthrough slots. *Ollie Nye* * Add test case for conflict with internal `@variant` variable. *David Backeus* * Document decision to not change naming convention recommendation to remove `-Component` suffix. *Joel Hawksley* * Fix typo in documentation. *Ryo.gift* * Add inline template example to benchmark script. *Andrew Tait* * Fix benchmark scripts. *Andrew Tait* * Run benchmarks in CI. *Joel Hawksley* ## 2.36.0 * Add `slot_type` helper method. *Jon Palmer* * Add test case for rendering a ViewComponent with slots in a controller. *Simon Fish* * Add example ViewComponent to documentation landing page. *Joel Hawksley* * Set maximum line length to 120. *Joel Hawksley* * Setting a collection slot with the plural setter (`component.items(array)` for `renders_many :items`) returns the array of slots. *Jon Palmer* * Update error messages to be more descriptive and helpful. *Joel Hawksley* * Raise an error if the slot name for renders_many is :contents *Simon Fish* ## 2.35.0 * Only load assets for Preview source highlighting if previews are enabled. *Joel Hawksley* * Fix references to moved documentation files. *Richard Macklin* * Ensure consistent indentation with Rubocop. *Joel Hawksley* * Bump `activesupport` upper bound from `< 7.0` to `< 8.0`. *Richard Macklin* * Add ERB Lint for a few basic rules. *Joel Hawksley* * Sort `gemspec` dependencies alphabetically. *Joel Hawksley* * Lock `method_source` at `1.0` to avoid open-ended dependency. *Joel Hawksley* * Require all PRs to include changelog entries. *Joel Hawksley* * Rename test app and move files under /test/sandbox. *Matt-Yorkley* * Make view_component_path config option available on ViewComponent::Base. *Matt-Yorkley* * Add @boardfish to Triage. *Joel Hawksley* * Adds support to change default components path (app/components) with `config.view_component.view_component_path`. *lfalcao* * Rename private instance variables (such as @variant) to reduce potential conflicts with subclasses. *Joel Hawksley* * Add documentation for configuration options. *Joel Hawksley* * Add view helper `preview_source` for rendering a source code preview below previews. * Add config option `config.view_component.show_previews_source` for enabling the source preview. *Johannes Engl* * Add documentation for compatibility with ActionText. *Jared Planter* ## 2.34.0 * Add the ability to enable ActiveSupport notifications (`!render.view_component` event) with `config.view_component.instrumentation_enabled`. *Svyatoslav Kryukov* * Add [Generators](https://viewcomponent.org/guide/generators.html) page to documentation. *Hans Lemuet* * Fix bug where ViewComponents didn't work in ActionMailers. *dark-panda* ## 2.33.0 * Add support for `_iteration` parameter when rendering in a collection *Will Cosgrove* * Don't raise an error when rendering empty components. *Alex Robbin* ## 2.32.0 * Enable previews by default in test environment. *Edouard Piron* * Fix test helper compatibility with Rails 7.0, TestRequest, and TestSession. *Leo Correa* * Add experimental `_output_postamble` lifecyle method. *Joel Hawksley* * Add compatibility notes on FAQ. *Matheus Richard* * Add Bridgetown on Compatibility documentation. *Matheus Richard* * Are you interested in building the future of ViewComponent? GitHub is looking to hire a Senior Engineer to work on Primer ViewComponents and ViewComponent. Apply here: [US/Canada](https://github.com/careers) / [Europe](https://boards.greenhouse.io/github/jobs/3132294). Feel free to reach out to joelhawksley@github.com with any questions. *Joel Hawksley* ## 2.31.2 * Patch XSS vulnerability in `ViewComponent::Translatable` module caused by improperly escaped interpolation arguments. *Cameron Dutro* ## 2.31.1 * Fix `DEPRECATION WARNING: before_render_check` when compiling `ViewComponent::Base` *Dave Kroondyk* ## 2.31.0 _Note: This release includes an underlying change to Slots that may affect incorrect usage of the API, where Slots were set on a line prefixed by `<%=`. The result of setting a Slot shouldn't be returned. (`<%`)_ * Add `#with_content` to allow setting content without a block. *Jordan Raine, Manuel Puyol* * Add `with_request_url` test helper. *Mario Schüttel* * Improve feature parity with Rails translations * Don't create a translation back end if the component has no translation file * Mark translation keys ending with `html` as HTML-safe * Always convert keys to String * Support multiple keys *Elia Schito* * Fix errors on `asset_url` helpers when `asset_host` has no protocol. *Elia Schito* * Prevent slots from overriding the `#content` method when registering a slot with that name. *Blake Williams* * Deprecate `with_slot` in favor of the new [slots API](https://viewcomponent.org/guide/slots.html). *Manuel Puyol* ## 2.30.0 * Deprecate `with_content_areas` in favor of [slots](https://viewcomponent.org/guide/slots.html). *Joel Hawksley* ## 2.29.0 * Allow Slot lambdas to share data from the parent component and allow chaining on the returned component. *Sjors Baltus, Blake Williams* * Experimental: Add `ViewComponent::Translatable` * `t` and `translate` now will look first into the sidecar YAML translations file. * `helpers.t` and `I18n.t` still reference the global Rails translation files. * `l` and `localize` will still reference the global Rails translation files. *Elia Schito* * Fix rendering output of pass through slots when using HAML. *Alex Robbin, Blake Williams* * Experimental: call `._sidecar_files` to fetch the sidecar files for a given list of extensions, for example passing `["yml", "yaml"]`. *Elia Schito* * Fix bug where a single `jbuilder` template matched multiple template handlers. *Niels Slot* ## 2.28.0 * Include SlotableV2 by default in Base. **Note:** It's no longer necessary to include `ViewComponent::SlotableV2` to use Slots. *Joel Hawksley* * Prepend Preview routes instead of appending, accounting for cases where host application has catchall route. *Joel Hawksley* * Fix bug where blocks passed to lambda slots will render incorrectly in certain situations. *Blake Williams* ## 2.27.0 * Allow customization of the controller used in component tests. *Alex Robbin* * Generate preview at overridden path if one exists when using `--preview` flag. *Nishiki Liu* ## 2.26.1 * Fix bug that raises when trying to use a collection before the component has been compiled. *Blake Williams* ## 2.26.0 * Delay evaluating component `content` in `render?`, preventing the `content` block from being evaluated when `render?` returns false. *Blake Williams* * Don't generate template when using `--inline` flag. *Hans Lemuet* * Add `--inline` option to the Haml and Slim generators *Hans Lemuet* ## 2.25.1 * Experimental: call `._after_compile` class method after a component is compiled. *Joel Hawksley* * Fix bug where SlotV2 was rendered as an HTML string when using Slim. *Manuel Puyol* ## 2.25.0 * Add `--preview` generator option to create an associated preview file. *Bob Maerten* * Add argument validation to avoid `content` override. *Manuel Puyol* ## 2.24.0 * Add `--inline` option to the erb generator. Prevents default erb template from being created and creates a component with a call method. *Nachiket Pusalkar* * Add test case for checking presence of `content` in `#render?`. *Joel Hawksley* * Rename `master` branch to `main`. *Joel Hawksley* ## 2.23.2 * Fix bug where rendering a component `with_collection` from a controller raised an error. *Joel Hawksley* ## 2.23.1 * Fixed out-of-order rendering bug in `ActionView::SlotableV2` *Blake Williams* ## 2.23.0 * Add `ActionView::SlotableV2` * `with_slot` becomes `renders_one`. * `with_slot collection: true` becomes `renders_many`. * Slot definitions now accept either a component class, component class name, or a lambda instead of a `class_name:` keyword argument. * Slots now support positional arguments. * Slots no longer use the `content` attribute to render content, instead relying on `to_s`. for example `<%= my_slot %>`. * Slot values are no longer set via the `slot` method, and instead use the name of the slot. *Blake Williams* * Add `frozen_string_literal: true` to generated component template. *Max Beizer* ## 2.22.1 * Revert refactor that broke rendering for some users. *Joel Hawksley* ## 2.22.0 * Add #with_variant to enable inline component variant rendering without template files. *Nathan Jones* ## 2.21.0 * Only compile components at application initialization if eager loading is enabled. *Joel Hawksley* ## 2.20.0 * Don't add `/test/components/previews` to preview_paths if directory doesn't exist. *Andy Holland* * Add `preview_controller` option to override the controller used for component previews. *Matt Swanson, Blake Williams, Juan Manuel Ramallo* ## 2.19.1 * Check if `Rails.application` is loaded. *Gleydson Tavares* * Add documentation for webpack configuration when using Stimulus controllers. *Ciprian Redinciuc* ## 2.19.0 * Extend documentation for using Stimulus within sidecar directories. *Ciprian Redinciuc* * Subclassed components inherit templates from parent. *Blake Williams* * Fix uninitialized constant error from `with_collection` when `eager_load` is disabled. *Josh Gross* ## 2.18.2 * Raise an error if controller or view context is accessed during initialize as they're only available in render. *Julian Nadeau* * Collate test coverage across CI builds, ensuring 100% test coverage. *Joel Hawksley* ## 2.18.1 * Fix bug where previews didn't work when monkey patch was disabled. *Mixer Gutierrez* ## 2.18.0 * Fix auto loading of previews (changes no longer require a server restart) *Matt Brictson* * Add `default_preview_layout` configuration option to load custom app/views/layouts. *Jared White, Juan Manuel Ramallo* * Calculate virtual_path once for all instances of a component class to improve performance. *Brad Parker* ## 2.17.1 * Fix bug where rendering Slot with empty block resulted in error. *Joel Hawksley* ## 2.17.0 * Slots return stripped HTML, removing leading and trailing whitespace. *Jason Long, Joel Hawksley* ## 2.16.0 * Add `--sidecar` option to the erb, haml and slim generators. Places the generated template in the sidecar directory. *Michael van Rooijen* ## 2.15.0 * Add support for templates as ViewComponent::Preview examples. *Juan Manuel Ramallo* ## 2.14.1 * Allow using `render_inline` in test when the render monkey patch is disabled with `config.view_component.render_monkey_patch_enabled = false` in versions of Rails < 6.1. *Clément Joubert* * Fix kwargs warnings in slotable. Fixes: ```console view_component/lib/view_component/slotable.rb:98: warning: Using the last argument as keyword parameters is deprecated; maybe ** should be added to the call view_component/test/app/components/slots_component.rb:18: warning: The called method `initialize' is defined here ``` *Eileen M. Uchitelle* ## 2.14.0 * Add `config.preview_paths` to support multiple locations of component preview files. Deprecate `config.preview_path`. *Tomas Celizna* * Only print warning about a missing capybara dependency if the `DEBUG` environment variable is set. *Richard Macklin* ## 2.13.0 * Add the ability to disable the render monkey patch with `config.view_component.render_monkey_patch_enabled`. In versions of Rails < 6.1, add `render_component` and `render_component_to_string` methods which can be used for rendering components instead of `render`. *Johannes Engl* ## 2.12.0 * Implement Slots as potential successor to Content Areas. *Jens Ljungblad, Brian Bugh, Jon Palmer, Joel Hawksley* ## 2.11.1 * Fix kwarg warnings in Ruby 2.7. *Joel Hawksley* ## 2.11.0 * Ensure Rails configuration is available within components. *Trevor Broaddus* * Fix bug where global Rails helpers are inaccessible from nested components. Before, `helpers` was pointing to parent component. *Franco Sebregondi* ## 2.10.0 * Raise an `ArgumentError` with a helpful message when Ruby can't parse a component class. *Max Beizer* ## 2.9.0 * Cache components per-request in development, preventing unnecessary recompilation during a single request. *Felipe Sateler* ## 2.8.0 * Add `before_render`, deprecating `before_render_check`. *Joel Hawksley* ## 2.7.0 * Add `rendered_component` method to `ViewComponent::TestHelpers` which exposes the raw output of the rendered component. *Richard Macklin* * Support sidecar directories for views and other assets. *Jon Palmer* ## 2.6.0 * Add `config.view_component.preview_route` to set the endpoint for component previews. By default `/rails/view_components` is used. *Juan Manuel Ramallo* * Raise error when initializer omits with_collection_parameter. *Joel Hawksley* ## 2.5.1 * Compile component before rendering collection. *Rainer Borene* ## v2.5.0 * Add counter variables when rendering collections. *Frank S* * Add the ability to access params from preview examples. *Fabio Cantoni* ## v2.4.0 * Add `#render_to_string` support. *Jarod Reid* * Declare explicit dependency on `activesupport`. *Richard Macklin* * Remove `autoload`s of internal modules (`Previewable`, `RenderMonkeyPatch`, `RenderingMonkeyPatch`). *Richard Macklin* * Remove `capybara` dependency. *Richard Macklin* ## v2.3.0 * Allow using inline render method(s) defined on a parent. *Simon Rand* * Fix bug where inline variant render methods would never be called. *Simon Rand* * ViewComponent preview index views use Rails internal layout instead of application's layout *Juan Manuel Ramallo* ## v2.2.2 * Add `Base.format` for better compatibility with `ActionView::Template`. *Joel Hawksley* ## v2.2.1 * Fix bug where template couldn't be found if `inherited` was redefined. *Joel Hawksley* ## v2.2.0 * Add support for `config.action_view.annotate_template_file_names` (coming in Rails 6.1). *Joel Hawksley* * Remove initializer requirement from the component. *Vasiliy Ermolovich* ## v2.1.0 * Support rendering collections (for example, `render(MyComponent.with_collection(@items))`). *Tim Clem* ## v2.0.0 * Move to `ViewComponent` namespace, removing all references to `ActionView`. * The gem name is now `view_component`. * ViewComponent previews are now accessed at `/rails/view_components`. * ViewComponents can _only_ be rendered with the instance syntax: `render(MyComponent.new)`. Support for all other syntaxes has been removed. * ActiveModel::Validations have been removed. ViewComponent generators no longer include validations. * In Rails 6.1, no monkey patching is used. * `to_component_class` has been removed. * All gem configuration is now in `config.view_component`. ## v1.17.0 * Support Ruby 2.4 in CI. *Andrew Mason* * ViewComponent generators don't not prompt for content requirement. *Joel Hawksley* * Add post-install message that gem has been renamed to `view_component`. *Joel Hawksley* ## v1.16.0 * Add `refute_component_rendered` test helper. *Joel Hawksley* * Check for Rails before invocation. *Dave Paola* * Allow components to be rendered without a template file (aka inline component). *Rainer Borene* ## v1.15.0 * Re-introduce ActionView::Component::TestHelpers. *Joel Hawksley* * Bypass monkey patch on Rails 6.1 builds. *Joel Hawksley* * Make `ActionView::Helpers::TagHelper` available in Previews. ```ruby def with_html_content render(MyComponent.new) do tag.div do content_tag(:span, "Hello") end end end ``` *Sean Doyle* ## v1.14.1 * Fix bug where generator created invalid test code. *Joel Hawksley* ## v1.14.0 * Rename ActionView::Component::Base to ViewComponent::Base *Joel Hawksley* ## v1.13.0 * Allow components to be rendered inside controllers. *Joel Hawksley* * Improve backtraces from exceptions raised in templates. *Blake Williams* ## v1.12.0 * Revert: Remove initializer requirement for Ruby 2.7+ *Joel Hawksley* * Restructure Railtie into Engine *Sean Doyle* * Allow components to override before_render_check *Joel Hawksley* ## v1.11.1 * Relax Capybara requirement. *Joel Hawksley* ## v1.11.0 * Add support for Capybara matchers. *Joel Hawksley* * Add erb, haml, & slim template generators *Asger Behncke Jacobsen* ## v1.10.0 * Deprecate all `render` syntaxes except for `render(MyComponent.new(foo: :bar))` *Joel Hawksley* ## v1.9.0 * Remove initializer requirement for Ruby 2.7+ *Dylan Clark* ## v1.8.1 * Run validation checks before calling `#render?`. *Ash Wilson* ## v1.8.0 * Remove the unneeded ComponentExamplesController and simplify preview rendering. *Jon Palmer* * Add `#render?` hook to allow components to be no-ops. *Kyle Fox* * Don't assume ApplicationController exists. *Jon Palmer* * Allow some additional checks to overrided render? *Sergey Malykh* * Fix generator placing namespaced components in the root directory. *Asger Behncke Jacobsen* * Fix cache test. *Sergey Malykh* ## v1.7.0 * Simplify validation of templates and compilation. *Jon Palmer* * Add support for multiple content areas. *Jon Palmer* ## v1.6.2 * Fix Uninitialized Constant error. *Jon Palmer* * Add basic github issue and PR templates. *Dylan Clark* * Update readme phrasing around previews. *Justin Coyne* ## v1.6.1 * Allow Previews to have no layout. *Jon Palmer* * Bump rack from 2.0.7 to 2.0.8. *Dependabot* * Compile components on application boot when eager loading is enabled. *Joel Hawksley* * Previews support content blocks. *Cesario Uy* * Follow Rails conventions. (refactor) *Rainer Borene* * Fix edge case issue with extracting variants from less conventional source_locations. *Ryan Workman* ## v1.6.0 * Avoid dropping elements in the render_inline test helper. *@dark-panda* * Add test for helpers.asset_url. *Christopher Coleman* * Add rudimentary compatibility with better_html. *Joel Hawksley* * Template-less variants fall back to default template. *Asger Behncke Jacobsen, Cesario Uy* * Generated tests use new naming convention. *Simon Træls Ravn* * Eliminate sqlite dependency. *Simon Dawson* * Add support for rendering components via #to_component_class *Vinicius Stock* ## v1.5.3 * Add support for RSpec to generators. *Dylan Clark, Ryan Workman* * Require controllers as part of setting autoload paths. *Joel Hawksley* ## v1.5.2 * Disable eager loading initializer. *Kasper Meyer* ## v1.5.1 * Update railties class to work with Rails 6. *Juan Manuel Ramallo* ## v1.5.0 Note: `actionview-component` is now loaded by requiring `actionview/component`, not `actionview/component/base`. * Fix issue with generating component method signatures. *Ryan Workman, Dylan Clark* * Create component generator. *Vinicius Stock* * Add helpers proxy. *Kasper Meyer* * Introduce ActionView::Component::Previews. *Juan Manuel Ramallo* ## v1.4.0 * Fix bug where components broke in application paths with periods. *Anton, Joel Hawksley* * Add support for `cache_if` in component templates. *Aaron Patterson, Joel Hawksley* * Add support for variants. *Juan Manuel Ramallo* * Fix bug in virtual path lookup. *Juan Manuel Ramallo* * Preselect the rendered component in render_inline. *Elia Schito* ## v1.3.6 * Allow template file names without format. *Joel Hawksley* * Add support for translations. *Juan Manuel Ramallo* ## v1.3.5 * Re-expose `controller` method. *Michael Emhofer, Joel Hawksley* * Gem version numbers are now accessible through `ActionView::Component::VERSION` *Richard Macklin* * Fix typo in README *ars moriendi* ## v1.3.4 * Template errors surface correct file and line number. *Justin Coyne* * Allow access to `request` inside components. *Joel Hawksley* ## v1.3.3 * Don't raise error when sidecar files that aren't templates exist. *Joel Hawksley* ## v1.3.2 * Support rendering views from inside component templates. *Patrick Sinclair* ## v1.3.1 * Fix bug where rendering nested content caused an error. *Joel Hawksley, Aaron Patterson* ## v1.3.0 * Components are rendered with enough controller context to support rendering of partials and forms. *Patrick Sinclair, Joel Hawksley, Aaron Patterson* ## v1.2.1 * `actionview-component` is now tested against Ruby 2.3/2.4 and Rails 5.0.0. ## v1.2.0 * The `render_component` test helper has been renamed to `render_inline`. `render_component` has been deprecated and will be removed in v2.0.0. *Joel Hawksley* * Components are now rendered with `render MyComponent, foo: :bar` syntax. The existing `render MyComponent.new(foo: :bar)` syntax has been deprecated and will be removed in v2.0.0. *Joel Hawksley* ## v1.1.0 * Components now inherit from ActionView::Component::Base *Joel Hawksley* ## v1.0.1 * Always recompile component templates outside production. *Joel Hawksley, John Hawthorn* ## v1.0.0 This release extracts the `ActionView::Component` library from the GitHub application. It will be published on RubyGems under the existing `actionview-component` gem name, as @chancancode has passed us ownership of the gem. view_component-3.8.0/README.md0000644000004100000410000000134214537061632016074 0ustar www-datawww-data![ViewComponent logo](/docs/logo/readme-light.svg#gh-light-mode-only) ![ViewComponent logo](/docs/logo/readme-dark.svg#gh-dark-mode-only) A framework for building reusable, testable & encapsulated view components in Ruby on Rails. ## Documentation See [viewcomponent.org](https://viewcomponent.org/) for documentation. ## Contributing 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](./docs/CONTRIBUTING.md) as well. ## License ViewComponent is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). view_component-3.8.0/view_component.gemspec0000644000004100000410000001670314537061632021225 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: view_component 3.8.0 ruby lib Gem::Specification.new do |s| s.name = "view_component".freeze s.version = "3.8.0" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.metadata = { "allowed_push_host" => "https://rubygems.org", "changelog_uri" => "https://github.com/ViewComponent/view_component/blob/main/docs/CHANGELOG.md", "source_code_uri" => "https://github.com/viewcomponent/view_component" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["ViewComponent Team".freeze] s.date = "2023-11-27" s.files = ["LICENSE.txt".freeze, "README.md".freeze, "app/assets/vendor/prism.css".freeze, "app/assets/vendor/prism.min.js".freeze, "app/controllers/concerns/view_component/preview_actions.rb".freeze, "app/controllers/view_components_controller.rb".freeze, "app/controllers/view_components_system_test_controller.rb".freeze, "app/helpers/preview_helper.rb".freeze, "app/views/test_mailer/test_email.html.erb".freeze, "app/views/view_components/_preview_source.html.erb".freeze, "app/views/view_components/index.html.erb".freeze, "app/views/view_components/preview.html.erb".freeze, "app/views/view_components/previews.html.erb".freeze, "docs/CHANGELOG.md".freeze, "lib/rails/generators/abstract_generator.rb".freeze, "lib/rails/generators/component/USAGE".freeze, "lib/rails/generators/component/component_generator.rb".freeze, "lib/rails/generators/component/templates/component.rb.tt".freeze, "lib/rails/generators/erb/component_generator.rb".freeze, "lib/rails/generators/erb/templates/component.html.erb.tt".freeze, "lib/rails/generators/haml/component_generator.rb".freeze, "lib/rails/generators/haml/templates/component.html.haml.tt".freeze, "lib/rails/generators/locale/component_generator.rb".freeze, "lib/rails/generators/preview/component_generator.rb".freeze, "lib/rails/generators/preview/templates/component_preview.rb.tt".freeze, "lib/rails/generators/rspec/component_generator.rb".freeze, "lib/rails/generators/rspec/templates/component_spec.rb.tt".freeze, "lib/rails/generators/slim/component_generator.rb".freeze, "lib/rails/generators/slim/templates/component.html.slim.tt".freeze, "lib/rails/generators/stimulus/component_generator.rb".freeze, "lib/rails/generators/stimulus/templates/component_controller.js.tt".freeze, "lib/rails/generators/tailwindcss/component_generator.rb".freeze, "lib/rails/generators/tailwindcss/templates/component.html.erb.tt".freeze, "lib/rails/generators/test_unit/component_generator.rb".freeze, "lib/rails/generators/test_unit/templates/component_test.rb.tt".freeze, "lib/view_component.rb".freeze, "lib/view_component/base.rb".freeze, "lib/view_component/capture_compatibility.rb".freeze, "lib/view_component/collection.rb".freeze, "lib/view_component/compile_cache.rb".freeze, "lib/view_component/compiler.rb".freeze, "lib/view_component/component_error.rb".freeze, "lib/view_component/config.rb".freeze, "lib/view_component/deprecation.rb".freeze, "lib/view_component/docs_builder_component.html.erb".freeze, "lib/view_component/docs_builder_component.rb".freeze, "lib/view_component/engine.rb".freeze, "lib/view_component/errors.rb".freeze, "lib/view_component/inline_template.rb".freeze, "lib/view_component/instrumentation.rb".freeze, "lib/view_component/preview.rb".freeze, "lib/view_component/rails/tasks/view_component.rake".freeze, "lib/view_component/render_component_helper.rb".freeze, "lib/view_component/render_component_to_string_helper.rb".freeze, "lib/view_component/render_monkey_patch.rb".freeze, "lib/view_component/render_to_string_monkey_patch.rb".freeze, "lib/view_component/rendering_component_helper.rb".freeze, "lib/view_component/rendering_monkey_patch.rb".freeze, "lib/view_component/slot.rb".freeze, "lib/view_component/slotable.rb".freeze, "lib/view_component/system_test_case.rb".freeze, "lib/view_component/system_test_helpers.rb".freeze, "lib/view_component/test_case.rb".freeze, "lib/view_component/test_helpers.rb".freeze, "lib/view_component/translatable.rb".freeze, "lib/view_component/use_helpers.rb".freeze, "lib/view_component/version.rb".freeze, "lib/view_component/with_content_helper.rb".freeze, "lib/yard/mattr_accessor_handler.rb".freeze] s.homepage = "https://viewcomponent.org".freeze s.licenses = ["MIT".freeze] s.required_ruby_version = Gem::Requirement.new(">= 2.7.0".freeze) s.rubygems_version = "3.3.15".freeze s.summary = "A framework for building reusable, testable & encapsulated view components in Ruby on Rails.".freeze if s.respond_to? :specification_version then s.specification_version = 4 end if s.respond_to? :add_runtime_dependency then s.add_runtime_dependency(%q.freeze, [">= 5.2.0", "< 8.0"]) s.add_development_dependency(%q.freeze, ["~> 2.4"]) s.add_development_dependency(%q.freeze, ["~> 2.12.0"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, ["~> 2"]) s.add_runtime_dependency(%q.freeze, ["~> 1.0"]) s.add_development_dependency(%q.freeze, [">= 0"]) s.add_development_dependency(%q.freeze, ["~> 6"]) s.add_development_dependency(%q.freeze, ["~> 2"]) s.add_development_dependency(%q.freeze, ["~> 1"]) s.add_runtime_dependency(%q.freeze, ["~> 1.0"]) s.add_development_dependency(%q.freeze, ["~> 5.18"]) s.add_development_dependency(%q.freeze, ["~> 0.13"]) s.add_development_dependency(%q.freeze, ["~> 13.0"]) s.add_development_dependency(%q.freeze, ["~> 1"]) s.add_development_dependency(%q.freeze, ["~> 0.22.0"]) s.add_development_dependency(%q.freeze, ["~> 0.9.1"]) s.add_development_dependency(%q.freeze, ["~> 5.1"]) s.add_development_dependency(%q.freeze, ["~> 3.4.2"]) s.add_development_dependency(%q.freeze, ["~> 1"]) s.add_development_dependency(%q.freeze, ["~> 0.9.25"]) s.add_development_dependency(%q.freeze, ["~> 0.0.1"]) else s.add_dependency(%q.freeze, [">= 5.2.0", "< 8.0"]) s.add_dependency(%q.freeze, ["~> 2.4"]) s.add_dependency(%q.freeze, ["~> 2.12.0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, ["~> 2"]) s.add_dependency(%q.freeze, ["~> 1.0"]) s.add_dependency(%q.freeze, [">= 0"]) s.add_dependency(%q.freeze, ["~> 6"]) s.add_dependency(%q.freeze, ["~> 2"]) s.add_dependency(%q.freeze, ["~> 1"]) s.add_dependency(%q.freeze, ["~> 1.0"]) s.add_dependency(%q.freeze, ["~> 5.18"]) s.add_dependency(%q.freeze, ["~> 0.13"]) s.add_dependency(%q.freeze, ["~> 13.0"]) s.add_dependency(%q.freeze, ["~> 1"]) s.add_dependency(%q.freeze, ["~> 0.22.0"]) s.add_dependency(%q.freeze, ["~> 0.9.1"]) s.add_dependency(%q.freeze, ["~> 5.1"]) s.add_dependency(%q.freeze, ["~> 3.4.2"]) s.add_dependency(%q.freeze, ["~> 1"]) s.add_dependency(%q.freeze, ["~> 0.9.25"]) s.add_dependency(%q.freeze, ["~> 0.0.1"]) end end view_component-3.8.0/app/0000755000004100000410000000000014537061632015375 5ustar www-datawww-dataview_component-3.8.0/app/helpers/0000755000004100000410000000000014537061632017037 5ustar www-datawww-dataview_component-3.8.0/app/helpers/preview_helper.rb0000644000004100000410000000523214537061632022406 0ustar www-datawww-data# frozen_string_literal: true module PreviewHelper # :nocov: include ActionView::Helpers::AssetUrlHelper if Rails.version.to_f < 6.1 # :nocov: AVAILABLE_PRISM_LANGUAGES = %w[ruby erb haml] FALLBACK_LANGUAGE = "ruby" def preview_source return if @render_args.nil? render "preview_source" end def prism_css_source_url serve_static_preview_assets? ? asset_path("prism.css", skip_pipeline: true) : "https://cdn.jsdelivr.net/npm/prismjs@1.28.0/themes/prism.min.css" end def prism_js_source_url serve_static_preview_assets? ? asset_path("prism.min.js", skip_pipeline: true) : "https://cdn.jsdelivr.net/npm/prismjs@1.28.0/prism.min.js" end def find_template_data(lookup_context:, template_identifier:) template = lookup_context.find_template(template_identifier) if Rails.version.to_f >= 6.1 || template.source.present? { source: template.source, prism_language_name: prism_language_name_by_template(template: template) } # :nocov: else # Fetch template source via finding it through preview paths # to accomodate source view when exclusively using templates # for previews for Rails < 6.1. all_template_paths = ViewComponent::Base.config.preview_paths.map do |preview_path| Dir.glob("#{preview_path}/**/*") end.flatten # Search for templates the contain `html`. matching_templates = all_template_paths.find_all do |path| path =~ /#{template_identifier}*.(html)/ end raise ViewComponent::NoMatchingTemplatesForPreviewError.new(template_identifier) if matching_templates.empty? raise ViewComponent::MultipleMatchingTemplatesForPreviewError.new(template_identifier) if matching_templates.size > 1 template_file_path = matching_templates.first template_source = File.read(template_file_path) prism_language_name = prism_language_name_by_template_path(template_file_path: template_file_path) { source: template_source, prism_language_name: prism_language_name } end # :nocov: end private def prism_language_name_by_template(template:) language = template.identifier.split(".").last return FALLBACK_LANGUAGE unless AVAILABLE_PRISM_LANGUAGES.include? language language end # :nocov: def prism_language_name_by_template_path(template_file_path:) language = template_file_path.gsub(".html", "").split(".").last return FALLBACK_LANGUAGE unless AVAILABLE_PRISM_LANGUAGES.include? language language end # :nocov: def serve_static_preview_assets? ViewComponent::Base.config.show_previews && Rails.application.config.public_file_server.enabled end end view_component-3.8.0/app/controllers/0000755000004100000410000000000014537061632017743 5ustar www-datawww-dataview_component-3.8.0/app/controllers/view_components_system_test_controller.rb0000644000004100000410000000156014537061632030417 0ustar www-datawww-data# frozen_string_literal: true class ViewComponentsSystemTestController < ActionController::Base # :nodoc: before_action :validate_test_env before_action :validate_file_path def self.temp_dir @_tmpdir ||= FileUtils.mkdir_p("./tmp/view_components/").first end def system_test_entrypoint render file: @path end private def validate_test_env raise ViewComponent::SystemTestControllerOnlyAllowedInTestError unless Rails.env.test? end # Ensure that the file path is valid and doesn't target files outside # the expected directory (e.g. via a path traversal or symlink attack) def validate_file_path base_path = ::File.realpath(self.class.temp_dir) @path = ::File.realpath(params.permit(:file)[:file], base_path) unless @path.start_with?(base_path) raise ViewComponent::SystemTestControllerNefariousPathError end end end view_component-3.8.0/app/controllers/view_components_controller.rb0000644000004100000410000000027314537061632025754 0ustar www-datawww-data# frozen_string_literal: true require "rails/application_controller" class ViewComponentsController < Rails::ApplicationController # :nodoc: include ViewComponent::PreviewActions end view_component-3.8.0/app/controllers/concerns/0000755000004100000410000000000014537061632021555 5ustar www-datawww-dataview_component-3.8.0/app/controllers/concerns/view_component/0000755000004100000410000000000014537061632024611 5ustar www-datawww-dataview_component-3.8.0/app/controllers/concerns/view_component/preview_actions.rb0000644000004100000410000000613314537061632030342 0ustar www-datawww-data# frozen_string_literal: true module ViewComponent module PreviewActions extend ActiveSupport::Concern included do prepend_view_path File.expand_path("../../../views", __dir__) around_action :set_locale, only: :previews before_action :require_local!, unless: :show_previews? content_security_policy(false) if respond_to?(:content_security_policy) # Including helpers here ensures that we're loading the # latest version of helpers if code-reloading is enabled helper :all if include_all_helpers end def index @previews = ViewComponent::Preview.all @page_title = "Component Previews" render "view_components/index", **determine_layout end def previews find_preview if params[:path] == @preview.preview_name @page_title = "Component Previews for #{@preview.preview_name}" render "view_components/previews", **determine_layout else prepend_application_view_paths prepend_preview_examples_view_path @example_name = File.basename(params[:path]) @render_args = @preview.render_args(@example_name, params: params.permit!) layout = determine_layout(@render_args[:layout], prepend_views: false)[:layout] locals = @render_args[:locals] opts = {} opts[:layout] = layout if layout.present? || layout == false opts[:locals] = locals if locals.present? render "view_components/preview", opts end end private # :doc: def default_preview_layout ViewComponent::Base.config.default_preview_layout end # :doc: def show_previews? ViewComponent::Base.config.show_previews end # :doc: def find_preview candidates = [] params[:path].to_s.scan(%r{/|$}) { candidates << Regexp.last_match.pre_match } preview = candidates.sort_by(&:length).reverse_each.detect { |candidate| ViewComponent::Preview.exists?(candidate) } if preview @preview = ViewComponent::Preview.find(preview) else raise AbstractController::ActionNotFound, "Component preview '#{params[:path]}' not found." end end def set_locale(&block) I18n.with_locale(params[:locale] || I18n.default_locale, &block) end # Returns either {} or {layout: value} depending on configuration def determine_layout(layout_override = nil, prepend_views: true) return {} unless defined?(Rails.root) layout_declaration = {} if !layout_override.nil? # Allow component-level override, even if false (thus no layout rendered) layout_declaration[:layout] = layout_override elsif default_preview_layout.present? layout_declaration[:layout] = default_preview_layout end prepend_application_view_paths if layout_declaration[:layout].present? && prepend_views layout_declaration end def prepend_application_view_paths prepend_view_path Rails.root.join("app/views") if defined?(Rails.root) end def prepend_preview_examples_view_path prepend_view_path(ViewComponent::Base.preview_paths) end end end view_component-3.8.0/app/assets/0000755000004100000410000000000014537061632016677 5ustar www-datawww-dataview_component-3.8.0/app/assets/vendor/0000755000004100000410000000000014537061632020174 5ustar www-datawww-dataview_component-3.8.0/app/assets/vendor/prism.css0000644000004100000410000000512314537061632022041 0ustar www-datawww-data/* PrismJS 1.28.0 https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+clike+erb+haml+markup-templating+ruby&plugins=line-highlight+highlight-keywords+normalize-whitespace */ code[class*=language-],pre[class*=language-]{color:#ccc;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.block-comment,.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#999}.token.punctuation{color:#ccc}.token.attr-name,.token.deleted,.token.namespace,.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.function,.token.number{color:#f08d49}.token.class-name,.token.constant,.token.property,.token.symbol{color:#f8c555}.token.atrule,.token.builtin,.token.important,.token.keyword,.token.selector{color:#cc99cd}.token.attr-value,.token.char,.token.regex,.token.string,.token.variable{color:#7ec699}.token.entity,.token.operator,.token.url{color:#67cdcc}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:green} pre[data-line]{position:relative;padding:1em 0 1em 3em}.line-highlight{position:absolute;left:0;right:0;padding:inherit 0;margin-top:1em;background:hsla(24,20%,50%,.08);background:linear-gradient(to right,hsla(24,20%,50%,.1) 70%,hsla(24,20%,50%,0));pointer-events:none;line-height:inherit;white-space:pre}@media print{.line-highlight{-webkit-print-color-adjust:exact;color-adjust:exact}}.line-highlight:before,.line-highlight[data-end]:after{content:attr(data-start);position:absolute;top:.4em;left:.6em;min-width:1em;padding:0 .5em;background-color:hsla(24,20%,50%,.4);color:#f4f1ef;font:bold 65%/1.5 sans-serif;text-align:center;vertical-align:.3em;border-radius:999px;text-shadow:none;box-shadow:0 1px #fff}.line-highlight[data-end]:after{content:attr(data-end);top:auto;bottom:.4em}.line-numbers .line-highlight:after,.line-numbers .line-highlight:before{content:none}pre[id].linkable-line-numbers span.line-numbers-rows{pointer-events:all}pre[id].linkable-line-numbers span.line-numbers-rows>span:before{cursor:pointer}pre[id].linkable-line-numbers span.line-numbers-rows>span:hover:before{background-color:rgba(128,128,128,.2)} view_component-3.8.0/app/assets/vendor/prism.min.js0000644000004100000410000006117114537061632022454 0ustar www-datawww-data/* PrismJS 1.28.0 https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+clike+erb+haml+markup-templating+ruby&plugins=line-highlight+highlight-keywords+normalize-whitespace */ var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(e){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,r={},a={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof i?new i(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/=g.reach);A+=w.value.length,w=w.next){var E=w.value;if(n.length>e.length)return;if(!(E instanceof i)){var P,L=1;if(y){if(!(P=l(b,A,e,m))||P.index>=e.length)break;var S=P.index,O=P.index+P[0].length,j=A;for(j+=w.value.length;S>=j;)j+=(w=w.next).value.length;if(A=j-=w.value.length,w.value instanceof i)continue;for(var C=w;C!==n.tail&&(jg.reach&&(g.reach=W);var z=w.prev;if(_&&(z=u(n,z,_),A+=_.length),c(n,z,L),w=u(n,z,new i(f,p?a.tokenize(N,p):N,k,N)),M&&u(n,w,M),L>1){var I={cause:f+","+d,reach:W};o(e,n,t,w.prev,A,I),g&&I.reach>g.reach&&(g.reach=I.reach)}}}}}}function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function u(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function c(e,n,t){for(var r=n.next,a=0;a"+i.content+""},!e.document)return e.addEventListener?(a.disableWorkerMessageHandler||e.addEventListener("message",(function(n){var t=JSON.parse(n.data),r=t.language,i=t.code,l=t.immediateClose;e.postMessage(a.highlight(i,a.languages[r],r)),l&&e.close()}),!1),a):a;var g=a.util.currentScript();function f(){a.manual||a.highlightAll()}if(g&&(a.filename=g.src,g.hasAttribute("data-manual")&&(a.manual=!0)),!a.manual){var h=document.readyState;"loading"===h||"interactive"===h&&g&&g.defer?document.addEventListener("DOMContentLoaded",f):window.requestAnimationFrame?window.requestAnimationFrame(f):window.setTimeout(f,16)}return a}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); Prism.languages.markup={comment:{pattern://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",(function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))})),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^$/i;var t={"included-cdata":{pattern://i,inside:s}};t["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var n={};n[a]={pattern:RegExp("(<__[^>]*>)(?:))*\\]\\]>|(?!)".replace(/__/g,(function(){return a})),"i"),lookbehind:!0,greedy:!0,inside:t},Prism.languages.insertBefore("markup","cdata",n)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(a,e){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp("(^|[\"'\\s])(?:"+a+")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))","i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[e,"language-"+e],inside:Prism.languages[e]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml; Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/}; !function(e){e.languages.ruby=e.languages.extend("clike",{comment:{pattern:/#.*|^=begin\s[\s\S]*?^=end/m,greedy:!0},"class-name":{pattern:/(\b(?:class|module)\s+|\bcatch\s+\()[\w.\\]+|\b[A-Z_]\w*(?=\s*\.\s*new\b)/,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:BEGIN|END|alias|and|begin|break|case|class|def|define_method|defined|do|each|else|elsif|end|ensure|extend|for|if|in|include|module|new|next|nil|not|or|prepend|private|protected|public|raise|redo|require|rescue|retry|return|self|super|then|throw|undef|unless|until|when|while|yield)\b/,operator:/\.{2,3}|&\.|===||[!=]?~|(?:&&|\|\||<<|>>|\*\*|[+\-*/%<>!^&|=])=?|[?:]/,punctuation:/[(){}[\].,;]/}),e.languages.insertBefore("ruby","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}});var n={pattern:/((?:^|[^\\])(?:\\{2})*)#\{(?:[^{}]|\{[^{}]*\})*\}/,lookbehind:!0,inside:{content:{pattern:/^(#\{)[\s\S]+(?=\}$)/,lookbehind:!0,inside:e.languages.ruby},delimiter:{pattern:/^#\{|\}$/,alias:"punctuation"}}};delete e.languages.ruby.function;var t="(?:"+["([^a-zA-Z0-9\\s{(\\[<=])(?:(?!\\1)[^\\\\]|\\\\[^])*\\1","\\((?:[^()\\\\]|\\\\[^]|\\((?:[^()\\\\]|\\\\[^])*\\))*\\)","\\{(?:[^{}\\\\]|\\\\[^]|\\{(?:[^{}\\\\]|\\\\[^])*\\})*\\}","\\[(?:[^\\[\\]\\\\]|\\\\[^]|\\[(?:[^\\[\\]\\\\]|\\\\[^])*\\])*\\]","<(?:[^<>\\\\]|\\\\[^]|<(?:[^<>\\\\]|\\\\[^])*>)*>"].join("|")+")",i='(?:"(?:\\\\.|[^"\\\\\r\n])*"|(?:\\b[a-zA-Z_]\\w*|[^\\s\0-\\x7F]+)[?!]?|\\$.)';e.languages.insertBefore("ruby","keyword",{"regex-literal":[{pattern:RegExp("%r"+t+"[egimnosux]{0,6}"),greedy:!0,inside:{interpolation:n,regex:/[\s\S]+/}},{pattern:/(^|[^/])\/(?!\/)(?:\[[^\r\n\]]+\]|\\.|[^[/\\\r\n])+\/[egimnosux]{0,6}(?=\s*(?:$|[\r\n,.;})#]))/,lookbehind:!0,greedy:!0,inside:{interpolation:n,regex:/[\s\S]+/}}],variable:/[@$]+[a-zA-Z_]\w*(?:[?!]|\b)/,symbol:[{pattern:RegExp("(^|[^:]):"+i),lookbehind:!0,greedy:!0},{pattern:RegExp("([\r\n{(,][ \t]*)"+i+"(?=:(?!:))"),lookbehind:!0,greedy:!0}],"method-definition":{pattern:/(\bdef\s+)\w+(?:\s*\.\s*\w+)?/,lookbehind:!0,inside:{function:/\b\w+$/,keyword:/^self\b/,"class-name":/^\w+/,punctuation:/\./}}}),e.languages.insertBefore("ruby","string",{"string-literal":[{pattern:RegExp("%[qQiIwWs]?"+t),greedy:!0,inside:{interpolation:n,string:/[\s\S]+/}},{pattern:/("|')(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|(?!\1)[^\\#\r\n])*\1/,greedy:!0,inside:{interpolation:n,string:/[\s\S]+/}},{pattern:/<<[-~]?([a-z_]\w*)[\r\n](?:.*[\r\n])*?[\t ]*\1/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<[-~]?[a-z_]\w*|\b[a-z_]\w*$/i,inside:{symbol:/\b\w+/,punctuation:/^<<[-~]?/}},interpolation:n,string:/[\s\S]+/}},{pattern:/<<[-~]?'([a-z_]\w*)'[\r\n](?:.*[\r\n])*?[\t ]*\1/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<[-~]?'[a-z_]\w*'|\b[a-z_]\w*$/i,inside:{symbol:/\b\w+/,punctuation:/^<<[-~]?'|'$/}},string:/[\s\S]+/}}],"command-literal":[{pattern:RegExp("%x"+t),greedy:!0,inside:{interpolation:n,command:{pattern:/[\s\S]+/,alias:"string"}}},{pattern:/`(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|[^\\`#\r\n])*`/,greedy:!0,inside:{interpolation:n,command:{pattern:/[\s\S]+/,alias:"string"}}}]}),delete e.languages.ruby.string,e.languages.insertBefore("ruby","number",{builtin:/\b(?:Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Fixnum|Float|Hash|IO|Integer|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|Stat|String|Struct|Symbol|TMS|Thread|ThreadGroup|Time|TrueClass)\b/,constant:/\b[A-Z][A-Z0-9_]*(?:[?!]|\b)/}),e.languages.rb=e.languages.ruby}(Prism); !function(e){function n(e,n){return"___"+e.toUpperCase()+n+"___"}Object.defineProperties(e.languages["markup-templating"]={},{buildPlaceholders:{value:function(t,a,r,o){if(t.language===a){var c=t.tokenStack=[];t.code=t.code.replace(r,(function(e){if("function"==typeof o&&!o(e))return e;for(var r,i=c.length;-1!==t.code.indexOf(r=n(a,i));)++i;return c[i]=e,r})),t.grammar=e.languages.markup}}},tokenizePlaceholders:{value:function(t,a){if(t.language===a&&t.tokenStack){t.grammar=e.languages[a];var r=0,o=Object.keys(t.tokenStack);!function c(i){for(var u=0;u=o.length);u++){var g=i[u];if("string"==typeof g||g.content&&"string"==typeof g.content){var l=o[r],s=t.tokenStack[l],f="string"==typeof g?g:g.content,p=n(a,l),k=f.indexOf(p);if(k>-1){++r;var m=f.substring(0,k),d=new e.Token(a,e.tokenize(s,t.grammar),"language-"+a,s),h=f.substring(k+p.length),v=[];m&&v.push.apply(v,c([m])),v.push(d),h&&v.push.apply(v,c([h])),"string"==typeof g?i.splice.apply(i,[u,1].concat(v)):g.content=v}}else g.content&&c(g.content)}return i}(t.tokens)}}}})}(Prism); !function(e){e.languages.erb={delimiter:{pattern:/^(\s*)<%=?|%>(?=\s*$)/,lookbehind:!0,alias:"punctuation"},ruby:{pattern:/\s*\S[\s\S]*/,alias:"language-ruby",inside:e.languages.ruby}},e.hooks.add("before-tokenize",(function(n){e.languages["markup-templating"].buildPlaceholders(n,"erb",/<%=?(?:[^\r\n]|[\r\n](?!=begin)|[\r\n]=begin\s(?:[^\r\n]|[\r\n](?!=end))*[\r\n]=end)+?%>/g)})),e.hooks.add("after-tokenize",(function(n){e.languages["markup-templating"].tokenizePlaceholders(n,"erb")}))}(Prism); !function(n){n.languages.haml={"multiline-comment":{pattern:/((?:^|\r?\n|\r)([\t ]*))(?:\/|-#).*(?:(?:\r?\n|\r)\2[\t ].+)*/,lookbehind:!0,alias:"comment"},"multiline-code":[{pattern:/((?:^|\r?\n|\r)([\t ]*)(?:[~-]|[&!]?=)).*,[\t ]*(?:(?:\r?\n|\r)\2[\t ].*,[\t ]*)*(?:(?:\r?\n|\r)\2[\t ].+)/,lookbehind:!0,inside:n.languages.ruby},{pattern:/((?:^|\r?\n|\r)([\t ]*)(?:[~-]|[&!]?=)).*\|[\t ]*(?:(?:\r?\n|\r)\2[\t ].*\|[\t ]*)*/,lookbehind:!0,inside:n.languages.ruby}],filter:{pattern:/((?:^|\r?\n|\r)([\t ]*)):[\w-]+(?:(?:\r?\n|\r)(?:\2[\t ].+|\s*?(?=\r?\n|\r)))+/,lookbehind:!0,inside:{"filter-name":{pattern:/^:[\w-]+/,alias:"symbol"}}},markup:{pattern:/((?:^|\r?\n|\r)[\t ]*)<.+/,lookbehind:!0,inside:n.languages.markup},doctype:{pattern:/((?:^|\r?\n|\r)[\t ]*)!!!(?: .+)?/,lookbehind:!0},tag:{pattern:/((?:^|\r?\n|\r)[\t ]*)[%.#][\w\-#.]*[\w\-](?:\([^)]+\)|\{(?:\{[^}]+\}|[^{}])+\}|\[[^\]]+\])*[\/<>]*/,lookbehind:!0,inside:{attributes:[{pattern:/(^|[^#])\{(?:\{[^}]+\}|[^{}])+\}/,lookbehind:!0,inside:n.languages.ruby},{pattern:/\([^)]+\)/,inside:{"attr-value":{pattern:/(=\s*)(?:"(?:\\.|[^\\"\r\n])*"|[^)\s]+)/,lookbehind:!0},"attr-name":/[\w:-]+(?=\s*!?=|\s*[,)])/,punctuation:/[=(),]/}},{pattern:/\[[^\]]+\]/,inside:n.languages.ruby}],punctuation:/[<>]/}},code:{pattern:/((?:^|\r?\n|\r)[\t ]*(?:[~-]|[&!]?=)).+/,lookbehind:!0,inside:n.languages.ruby},interpolation:{pattern:/#\{[^}]+\}/,inside:{delimiter:{pattern:/^#\{|\}$/,alias:"punctuation"},ruby:{pattern:/[\s\S]+/,inside:n.languages.ruby}}},punctuation:{pattern:/((?:^|\r?\n|\r)[\t ]*)[~=\-&!]+/,lookbehind:!0}};for(var e=["css",{filter:"coffee",language:"coffeescript"},"erb","javascript","less","markdown","ruby","scss","textile"],t={},r=0,a=e.length;r ",document.body.appendChild(t),e=38===t.offsetHeight,document.body.removeChild(t)}return e}()?parseInt:parseFloat)(getComputedStyle(o).lineHeight),p=Prism.util.isActive(o,t),g=o.querySelector("code"),m=p?o:g||o,v=[],y=g.textContent.match(n),b=y?y.length+1:1,A=g&&m!=g?function(e,t){var i=getComputedStyle(e),n=getComputedStyle(t);function r(e){return+e.substr(0,e.length-2)}return t.offsetTop+r(n.borderTopWidth)+r(n.paddingTop)-r(i.paddingTop)}(o,g):0;h.forEach((function(e){var t=e.split("-"),i=+t[0],n=+t[1]||i;if(!((n=Math.min(b,n))i&&r.setAttribute("data-end",String(n)),r.style.top=(i-d-1)*f+A+"px",r.textContent=new Array(n-i+2).join(" \n")}));v.push((function(){r.style.width=o.scrollWidth+"px"})),v.push((function(){m.appendChild(r)}))}}));var P=o.id;if(p&&Prism.util.isActive(o,i)&&P){l(o,i)||v.push((function(){o.classList.add(i)}));var E=parseInt(o.getAttribute("data-start")||"1");s(".line-numbers-rows > span",o).forEach((function(e,t){var i=t+E;e.onclick=function(){var e=P+"."+i;r=!1,location.hash=e,setTimeout((function(){r=!0}),1)}}))}return function(){v.forEach(a)}}};var o=0;Prism.hooks.add("before-sanity-check",(function(e){var t=e.element.parentElement;if(u(t)){var i=0;s(".line-highlight",t).forEach((function(e){i+=e.textContent.length,e.parentNode.removeChild(e)})),i&&/^(?: \n)+$/.test(e.code.slice(-i))&&(e.code=e.code.slice(0,-i))}})),Prism.hooks.add("complete",(function e(i){var n=i.element.parentElement;if(u(n)){clearTimeout(o);var r=Prism.plugins.lineNumbers,s=i.plugins&&i.plugins.lineNumbers;l(n,t)&&r&&!s?Prism.hooks.add("line-numbers",e):(Prism.plugins.lineHighlight.highlightLines(n)(),o=setTimeout(c,1))}})),window.addEventListener("hashchange",c),window.addEventListener("resize",(function(){s("pre").filter(u).map((function(e){return Prism.plugins.lineHighlight.highlightLines(e)})).forEach(a)}))}function s(e,t){return Array.prototype.slice.call((t||document).querySelectorAll(e))}function l(e,t){return e.classList.contains(t)}function a(e){e()}function u(e){return!!(e&&/pre/i.test(e.nodeName)&&(e.hasAttribute("data-line")||e.id&&Prism.util.isActive(e,i)))}function c(){var e=location.hash.slice(1);s(".temporary.line-highlight").forEach((function(e){e.parentNode.removeChild(e)}));var t=(e.match(/\.([\d,-]+)$/)||[,""])[1];if(t&&!document.getElementById(e)){var i=e.slice(0,e.lastIndexOf(".")),n=document.getElementById(i);n&&(n.hasAttribute("data-line")||n.setAttribute("data-line",""),Prism.plugins.lineHighlight.highlightLines(n,t,"temporary ")(),r&&document.querySelector(".temporary.line-highlight").scrollIntoView())}}}(); "undefined"!=typeof Prism&&Prism.hooks.add("wrap",(function(e){"keyword"===e.type&&e.classes.push("keyword-"+e.content)})); !function(){if("undefined"!=typeof Prism){var e=Object.assign||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n]);return e},t={"remove-trailing":"boolean","remove-indent":"boolean","left-trim":"boolean","right-trim":"boolean","break-lines":"number",indent:"number","remove-initial-line-feed":"boolean","tabs-to-spaces":"number","spaces-to-tabs":"number"};n.prototype={setDefaults:function(t){this.defaults=e(this.defaults,t)},normalize:function(t,n){for(var r in n=e(this.defaults,n)){var i=r.replace(/-(\w)/g,(function(e,t){return t.toUpperCase()}));"normalize"!==r&&"setDefaults"!==i&&n[r]&&this[i]&&(t=this[i].call(this,t,n[r]))}return t},leftTrim:function(e){return e.replace(/^\s+/,"")},rightTrim:function(e){return e.replace(/\s+$/,"")},tabsToSpaces:function(e,t){return t=0|t||4,e.replace(/\t/g,new Array(++t).join(" "))},spacesToTabs:function(e,t){return t=0|t||4,e.replace(RegExp(" {"+t+"}","g"),"\t")},removeTrailing:function(e){return e.replace(/\s*?$/gm,"")},removeInitialLineFeed:function(e){return e.replace(/^(?:\r?\n|\r)/,"")},removeIndent:function(e){var t=e.match(/^[^\S\n\r]*(?=\S)/gm);return t&&t[0].length?(t.sort((function(e,t){return e.length-t.length})),t[0].length?e.replace(RegExp("^"+t[0],"gm"),""):e):e},indent:function(e,t){return e.replace(/^[^\S\n\r]*(?=\S)/gm,new Array(++t).join("\t")+"$&")},breakLines:function(e,t){t=!0===t?80:0|t||80;for(var n=e.split("\n"),i=0;it&&(o[l]="\n"+o[l],a=s)}n[i]=o.join("")}return n.join("\n")}},"undefined"!=typeof module&&module.exports&&(module.exports=n),Prism.plugins.NormalizeWhitespace=new n({"remove-trailing":!0,"remove-indent":!0,"left-trim":!0,"right-trim":!0}),Prism.hooks.add("before-sanity-check",(function(e){var n=Prism.plugins.NormalizeWhitespace;if((!e.settings||!1!==e.settings["whitespace-normalization"])&&Prism.util.isActive(e.element,"whitespace-normalization",!0))if(e.element&&e.element.parentNode||!e.code){var r=e.element.parentNode;if(e.code&&r&&"pre"===r.nodeName.toLowerCase()){for(var i in null==e.settings&&(e.settings={}),t)if(Object.hasOwnProperty.call(t,i)){var o=t[i];if(r.hasAttribute("data-"+i))try{var a=JSON.parse(r.getAttribute("data-"+i)||"true");typeof a===o&&(e.settings[i]=a)}catch(e){}}for(var l=r.childNodes,s="",c="",u=!1,m=0;m <% if ViewComponent::Base.config.render_monkey_patch_enabled || Rails.version.to_f >= 6.1 %> <%= render(@render_args[:component], @render_args[:args], &@render_args[:block]) %> <% else %> <%= render_component(@render_args[:component], &@render_args[:block]) %> <% end %> <% else %> <%= render template: @render_args[:template], locals: @render_args[:locals] || {} %> <% end %> <% if ViewComponent::Base.config.show_previews_source %> <%= preview_source %> <% end %> view_component-3.8.0/app/views/view_components/index.html.erb0000644000004100000410000000054214537061632024516 0ustar www-datawww-data<% @previews.each do |preview| %>

<%= link_to preview.preview_name.titleize, preview_view_component_path(preview.preview_name) %>

    <% preview.examples.each do |preview_example| %>
  • <%= link_to preview_example, preview_view_component_path("#{preview.preview_name}/#{preview_example}") %>
  • <% end %>
<% end %> view_component-3.8.0/app/views/view_components/_preview_source.html.erb0000644000004100000410000000130114537061632026601 0ustar www-datawww-data

Source:

    <% if @render_args[:component] %>
      
        <%= h @preview.preview_source(@example_name) %>
      
    <% else %>
      <% template_data = find_template_data(lookup_context: @view_renderer.lookup_context, template_identifier: @render_args[:template]) %>
      
        <%= h template_data[:source] %>
      
    <% end %>
  
view_component-3.8.0/app/views/view_components/previews.html.erb0000644000004100000410000000033214537061632025250 0ustar www-datawww-data

<%= @preview.preview_name.titleize %>

    <% @preview.examples.each do |example| %>
  • <%= link_to example, preview_view_component_path("#{@preview.preview_name}/#{example}") %>
  • <% end %>
view_component-3.8.0/app/views/test_mailer/0000755000004100000410000000000014537061632021042 5ustar www-datawww-dataview_component-3.8.0/app/views/test_mailer/test_email.html.erb0000644000004100000410000000004214537061632024621 0ustar www-datawww-data<%= render MailerComponent.new %> view_component-3.8.0/lib/0000755000004100000410000000000014537061632015363 5ustar www-datawww-dataview_component-3.8.0/lib/yard/0000755000004100000410000000000014537061632016322 5ustar www-datawww-dataview_component-3.8.0/lib/yard/mattr_accessor_handler.rb0000644000004100000410000000102114537061632023347 0ustar www-datawww-data# frozen_string_literal: true module YARD # YARD Handler to parse `mattr_accessor` calls. class MattrAccessorHandler < YARD::Handlers::Ruby::Base handles method_call(:mattr_accessor) namespace_only process do name = statement.parameters.first.jump(:tstring_content, :ident).source object = YARD::CodeObjects::MethodObject.new(namespace, name) register(object) parse_block(statement.last, owner: object) object.dynamic = true object[:mattr_accessor] = true end end end view_component-3.8.0/lib/rails/0000755000004100000410000000000014537061632016475 5ustar www-datawww-dataview_component-3.8.0/lib/rails/generators/0000755000004100000410000000000014537061632020646 5ustar www-datawww-dataview_component-3.8.0/lib/rails/generators/test_unit/0000755000004100000410000000000014537061632022664 5ustar www-datawww-dataview_component-3.8.0/lib/rails/generators/test_unit/component_generator.rb0000644000004100000410000000077414537061632027271 0ustar www-datawww-data# frozen_string_literal: true module TestUnit module Generators class ComponentGenerator < ::Rails::Generators::NamedBase source_root File.expand_path("templates", __dir__) check_class_collision suffix: "ComponentTest" def create_test_file template "component_test.rb", File.join("test/components", class_path, "#{file_name}_component_test.rb") end private def file_name @_file_name ||= super.sub(/_component\z/i, "") end end end end view_component-3.8.0/lib/rails/generators/test_unit/templates/0000755000004100000410000000000014537061632024662 5ustar www-datawww-dataview_component-3.8.0/lib/rails/generators/test_unit/templates/component_test.rb.tt0000644000004100000410000000053614537061632030702 0ustar www-datawww-data# frozen_string_literal: true require "test_helper" class <%= class_name %>ComponentTest < ViewComponent::TestCase def test_component_renders_something_useful # assert_equal( # %(Hello, components!), # render_inline(<%= class_name %>Component.new(message: "Hello, components!")).css("span").to_html # ) end end view_component-3.8.0/lib/rails/generators/locale/0000755000004100000410000000000014537061632022105 5ustar www-datawww-dataview_component-3.8.0/lib/rails/generators/locale/component_generator.rb0000644000004100000410000000265314537061632026510 0ustar www-datawww-data# frozen_string_literal: true require "rails/generators/abstract_generator" module Locale module Generators class ComponentGenerator < ::Rails::Generators::NamedBase include ViewComponent::AbstractGenerator source_root File.expand_path("templates", __dir__) argument :attributes, type: :array, default: [], banner: "attribute" class_option :sidecar, type: :boolean, default: false def create_locale_file if ViewComponent::Base.config.generate.distinct_locale_files I18n.available_locales.each do |locale| create_file destination(locale), translations_hash([locale]).to_yaml end else create_file destination, translations_hash(I18n.available_locales).to_yaml end end private def translations_hash(locales = [:en]) locales.map { |locale| [locale.to_s, translation_keys] }.to_h end def translation_keys keys = attributes.map(&:name) keys = %w[hello] if keys.empty? keys.map { |name| [name, name.capitalize] }.to_h end def destination(locale = nil) extention = ".#{locale}" if locale if sidecar? File.join(component_path, class_path, "#{file_name}_component", "#{file_name}_component#{extention}.yml") else File.join(component_path, class_path, "#{file_name}_component#{extention}.yml") end end end end end view_component-3.8.0/lib/rails/generators/component/0000755000004100000410000000000014537061632022650 5ustar www-datawww-dataview_component-3.8.0/lib/rails/generators/component/component_generator.rb0000644000004100000410000000375114537061632027253 0ustar www-datawww-data# frozen_string_literal: true require "rails/generators/abstract_generator" module Rails module Generators class ComponentGenerator < Rails::Generators::NamedBase include ViewComponent::AbstractGenerator source_root File.expand_path("templates", __dir__) argument :attributes, type: :array, default: [], banner: "attribute" check_class_collision suffix: "Component" class_option :inline, type: :boolean, default: false class_option :locale, type: :boolean, default: ViewComponent::Base.config.generate.locale class_option :parent, type: :string, desc: "The parent class for the generated component" class_option :preview, type: :boolean, default: ViewComponent::Base.config.generate.preview class_option :sidecar, type: :boolean, default: false class_option :stimulus, type: :boolean, default: ViewComponent::Base.config.generate.stimulus_controller def create_component_file template "component.rb", File.join(component_path, class_path, "#{file_name}_component.rb") end hook_for :test_framework hook_for :preview, type: :boolean hook_for :stimulus, type: :boolean hook_for :locale, type: :boolean hook_for :template_engine do |instance, template_engine| instance.invoke template_engine, [instance.name] end private def parent_class return options[:parent] if options[:parent] ViewComponent::Base.config.component_parent_class || default_parent_class end def initialize_signature return if attributes.blank? attributes.map { |attr| "#{attr.name}:" }.join(", ") end def initialize_body attributes.map { |attr| "@#{attr.name} = #{attr.name}" }.join("\n ") end def initialize_call_method_for_inline? options["inline"] end def default_parent_class defined?(ApplicationComponent) ? ApplicationComponent : ViewComponent::Base end end end end view_component-3.8.0/lib/rails/generators/component/templates/0000755000004100000410000000000014537061632024646 5ustar www-datawww-dataview_component-3.8.0/lib/rails/generators/component/templates/component.rb.tt0000644000004100000410000000062714537061632027630 0ustar www-datawww-data# frozen_string_literal: true class <%= class_name %>Component < <%= parent_class %> <%- if initialize_signature -%> def initialize(<%= initialize_signature %>) <%= initialize_body %> end <%- end -%> <%- if initialize_call_method_for_inline? -%> def call content_tag :h1, "Hello world!"<%= ", data: { controller: \"#{stimulus_controller}\" }" if options["stimulus"] %> end <%- end -%> end view_component-3.8.0/lib/rails/generators/component/USAGE0000644000004100000410000000073314537061632023442 0ustar www-datawww-dataDescription: ============ Creates a new component and test. Pass the component name, either CamelCased or under_scored, and an optional list of attributes as arguments. Example: ======== bin/rails generate component Profile name age creates a Profile component and test: Component: app/components/profile_component.rb Template: app/components/profile_component.html.erb Test: test/components/profile_component_test.rb view_component-3.8.0/lib/rails/generators/erb/0000755000004100000410000000000014537061632021416 5ustar www-datawww-dataview_component-3.8.0/lib/rails/generators/erb/component_generator.rb0000644000004100000410000000132714537061632026016 0ustar www-datawww-data# frozen_string_literal: true require "rails/generators/erb" require "rails/generators/abstract_generator" module Erb module Generators class ComponentGenerator < Base include ViewComponent::AbstractGenerator source_root File.expand_path("templates", __dir__) class_option :sidecar, type: :boolean, default: false class_option :inline, type: :boolean, default: false class_option :stimulus, type: :boolean, default: false def engine_name "erb" end def copy_view_file super end private def data_attributes if options["stimulus"] " data-controller=\"#{stimulus_controller}\"" end end end end end view_component-3.8.0/lib/rails/generators/erb/templates/0000755000004100000410000000000014537061632023414 5ustar www-datawww-dataview_component-3.8.0/lib/rails/generators/erb/templates/component.html.erb.tt0000644000004100000410000000010514537061632027475 0ustar www-datawww-data>Add <%= class_name %> template here view_component-3.8.0/lib/rails/generators/stimulus/0000755000004100000410000000000014537061632022533 5ustar www-datawww-dataview_component-3.8.0/lib/rails/generators/stimulus/component_generator.rb0000644000004100000410000000210514537061632027126 0ustar www-datawww-data# frozen_string_literal: true require "rails/generators/abstract_generator" module Stimulus module Generators class ComponentGenerator < ::Rails::Generators::NamedBase include ViewComponent::AbstractGenerator source_root File.expand_path("templates", __dir__) class_option :sidecar, type: :boolean, default: false def create_stimulus_controller template "component_controller.js", destination end def stimulus_module return "stimulus" if legacy_stimulus? "@hotwired/stimulus" end private def destination if sidecar? File.join(component_path, class_path, "#{file_name}_component", "#{file_name}_component_controller.js") else File.join(component_path, class_path, "#{file_name}_component_controller.js") end end def legacy_stimulus? package_json_pathname = Rails.root.join("package.json") package_json_pathname.exist? && JSON.parse(package_json_pathname.read).dig("dependencies", "stimulus").present? end end end end view_component-3.8.0/lib/rails/generators/stimulus/templates/0000755000004100000410000000000014537061632024531 5ustar www-datawww-dataview_component-3.8.0/lib/rails/generators/stimulus/templates/component_controller.js.tt0000644000004100000410000000024714537061632031765 0ustar www-datawww-dataimport { Controller } from "<%= stimulus_module %>"; export default class extends Controller { connect() { console.log("Hello, Stimulus!", this.element); } } view_component-3.8.0/lib/rails/generators/preview/0000755000004100000410000000000014537061632022327 5ustar www-datawww-dataview_component-3.8.0/lib/rails/generators/preview/component_generator.rb0000644000004100000410000000240514537061632026725 0ustar www-datawww-data# frozen_string_literal: true module Preview module Generators class ComponentGenerator < ::Rails::Generators::NamedBase source_root File.expand_path("templates", __dir__) class_option :preview_path, type: :string, desc: "Path for previews, required when multiple preview paths are configured", default: ViewComponent::Base.config.generate.preview_path argument :attributes, type: :array, default: [], banner: "attribute" check_class_collision suffix: "ComponentPreview" def create_preview_file preview_paths = ViewComponent::Base.config.preview_paths optional_path = options[:preview_path] return if preview_paths.count > 1 && optional_path.blank? path_prefix = if optional_path.present? optional_path else preview_paths.one? ? preview_paths.first : "test/components/previews" end template "component_preview.rb", File.join(path_prefix, class_path, "#{file_name}_component_preview.rb") end private def file_name @_file_name ||= super.sub(/_component\z/i, "") end def render_signature return if attributes.blank? attributes.map { |attr| %(#{attr.name}: "#{attr.name}") }.join(", ") end end end end view_component-3.8.0/lib/rails/generators/preview/templates/0000755000004100000410000000000014537061632024325 5ustar www-datawww-dataview_component-3.8.0/lib/rails/generators/preview/templates/component_preview.rb.tt0000644000004100000410000000032514537061632031043 0ustar www-datawww-data# frozen_string_literal: true class <%= class_name %>ComponentPreview < ViewComponent::Preview def default render(<%= class_name %>Component.new<%= "(#{render_signature})" if render_signature %>) end end view_component-3.8.0/lib/rails/generators/abstract_generator.rb0000644000004100000410000000210414537061632025041 0ustar www-datawww-data# frozen_string_literal: true module ViewComponent module AbstractGenerator def copy_view_file template "component.html.#{engine_name}", destination unless options["inline"] end private def destination File.join(destination_directory, "#{destination_file_name}.html.#{engine_name}") end def destination_directory if sidecar? File.join(component_path, class_path, destination_file_name) else File.join(component_path, class_path) end end def destination_file_name "#{file_name}_component" end def file_name @_file_name ||= super.sub(/_component\z/i, "") end def component_path ViewComponent::Base.config.view_component_path end def stimulus_controller if options["stimulus"] File.join(destination_directory, destination_file_name) .sub("#{component_path}/", "") .tr("_", "-") .gsub("/", "--") end end def sidecar? options["sidecar"] || ViewComponent::Base.config.generate.sidecar end end end view_component-3.8.0/lib/rails/generators/tailwindcss/0000755000004100000410000000000014537061632023172 5ustar www-datawww-dataview_component-3.8.0/lib/rails/generators/tailwindcss/component_generator.rb0000644000004100000410000000041014537061632027562 0ustar www-datawww-data# frozen_string_literal: true require "rails/generators/erb/component_generator" module Tailwindcss module Generators class ComponentGenerator < Erb::Generators::ComponentGenerator source_root File.expand_path("templates", __dir__) end end end view_component-3.8.0/lib/rails/generators/tailwindcss/templates/0000755000004100000410000000000014537061632025170 5ustar www-datawww-dataview_component-3.8.0/lib/rails/generators/tailwindcss/templates/component.html.erb.tt0000644000004100000410000000010514537061632031251 0ustar www-datawww-data>Add <%= class_name %> template here view_component-3.8.0/lib/rails/generators/rspec/0000755000004100000410000000000014537061632021762 5ustar www-datawww-dataview_component-3.8.0/lib/rails/generators/rspec/component_generator.rb0000644000004100000410000000070514537061632026361 0ustar www-datawww-data# frozen_string_literal: true module Rspec module Generators class ComponentGenerator < ::Rails::Generators::NamedBase source_root File.expand_path("templates", __dir__) def create_test_file template "component_spec.rb", File.join("spec/components", class_path, "#{file_name}_component_spec.rb") end private def file_name @_file_name ||= super.sub(/_component\z/i, "") end end end end view_component-3.8.0/lib/rails/generators/rspec/templates/0000755000004100000410000000000014537061632023760 5ustar www-datawww-dataview_component-3.8.0/lib/rails/generators/rspec/templates/component_spec.rb.tt0000644000004100000410000000061414537061632027750 0ustar www-datawww-data# frozen_string_literal: true require "rails_helper" RSpec.describe <%= class_name %>Component, type: :component do pending "add some examples to (or delete) #{__FILE__}" # it "renders something useful" do # expect( # render_inline(described_class.new(attr: "value")) { "Hello, components!" }.css("p").to_html # ).to include( # "Hello, components!" # ) # end end view_component-3.8.0/lib/rails/generators/haml/0000755000004100000410000000000014537061632021567 5ustar www-datawww-dataview_component-3.8.0/lib/rails/generators/haml/component_generator.rb0000644000004100000410000000071714537061632026171 0ustar www-datawww-data# frozen_string_literal: true require "rails/generators/erb/component_generator" module Haml module Generators class ComponentGenerator < Erb::Generators::ComponentGenerator include ViewComponent::AbstractGenerator source_root File.expand_path("templates", __dir__) class_option :sidecar, type: :boolean, default: false def engine_name "haml" end def copy_view_file super end end end end view_component-3.8.0/lib/rails/generators/haml/templates/0000755000004100000410000000000014537061632023565 5ustar www-datawww-dataview_component-3.8.0/lib/rails/generators/haml/templates/component.html.haml.tt0000644000004100000410000000005114537061632030017 0ustar www-datawww-data%div Add <%= class_name %> template here view_component-3.8.0/lib/rails/generators/slim/0000755000004100000410000000000014537061632021612 5ustar www-datawww-dataview_component-3.8.0/lib/rails/generators/slim/component_generator.rb0000644000004100000410000000071714537061632026214 0ustar www-datawww-data# frozen_string_literal: true require "rails/generators/erb/component_generator" module Slim module Generators class ComponentGenerator < Erb::Generators::ComponentGenerator include ViewComponent::AbstractGenerator source_root File.expand_path("templates", __dir__) class_option :sidecar, type: :boolean, default: false def engine_name "slim" end def copy_view_file super end end end end view_component-3.8.0/lib/rails/generators/slim/templates/0000755000004100000410000000000014537061632023610 5ustar www-datawww-dataview_component-3.8.0/lib/rails/generators/slim/templates/component.html.slim.tt0000644000004100000410000000005014537061632030064 0ustar www-datawww-datadiv Add <%= class_name %> template here view_component-3.8.0/lib/view_component.rb0000644000004100000410000000111414537061632020741 0ustar www-datawww-data# frozen_string_literal: true require "action_view" require "active_support/dependencies/autoload" module ViewComponent extend ActiveSupport::Autoload autoload :Base autoload :CaptureCompatibility autoload :Compiler autoload :CompileCache autoload :ComponentError autoload :Config autoload :Deprecation autoload :InlineTemplate autoload :Instrumentation autoload :Preview autoload :TestHelpers autoload :SystemTestHelpers autoload :TestCase autoload :SystemTestCase autoload :Translatable end require "view_component/engine" if defined?(Rails::Engine) view_component-3.8.0/lib/view_component/0000755000004100000410000000000014537061632020417 5ustar www-datawww-dataview_component-3.8.0/lib/view_component/translatable.rb0000644000004100000410000000760214537061632023425 0ustar www-datawww-data# frozen_string_literal: true require "erb" require "set" require "i18n" require "active_support/concern" module ViewComponent module Translatable extend ActiveSupport::Concern HTML_SAFE_TRANSLATION_KEY = /(?:_|\b)html\z/ included do class_attribute :i18n_backend, instance_writer: false, instance_predicate: false end class_methods do def i18n_scope @i18n_scope ||= virtual_path.sub(%r{^/}, "").gsub(%r{/_?}, ".") end def build_i18n_backend return if compiled? self.i18n_backend = if (translation_files = sidecar_files(%w[yml yaml])).any? # Returning nil cleans up if translations file has been removed since the last compilation I18nBackend.new( i18n_scope: i18n_scope, load_paths: translation_files ) end end def i18n_key(key, scope = nil) scope = scope.join(".") if scope.is_a? Array key = key&.to_s unless key.is_a?(String) key = "#{scope}.#{key}" if scope key = "#{i18n_scope}#{key}" if key.start_with?(".") key end def translate(key = nil, **options) return key.map { |k| translate(k, **options) } if key.is_a?(Array) ensure_compiled locale = options.delete(:locale) || ::I18n.locale key = i18n_key(key, options.delete(:scope)) i18n_backend.translate(locale, key, options) end alias_method :t, :translate end class I18nBackend < ::I18n::Backend::Simple EMPTY_HASH = {}.freeze def initialize(i18n_scope:, load_paths:) @i18n_scope = i18n_scope.split(".").map(&:to_sym) @load_paths = load_paths end # Ensure the Simple backend won't load paths from ::I18n.load_path def load_translations super(@load_paths) end def scope_data(data) @i18n_scope.reverse_each do |part| data = {part => data} end data end def store_translations(locale, data, options = EMPTY_HASH) super(locale, scope_data(data), options) end end def translate(key = nil, **options) raise ViewComponent::TranslateCalledBeforeRenderError if view_context.nil? return super unless i18n_backend return key.map { |k| translate(k, **options) } if key.is_a?(Array) locale = options.delete(:locale) || ::I18n.locale key = self.class.i18n_key(key, options.delete(:scope)) as_html = HTML_SAFE_TRANSLATION_KEY.match?(key) html_escape_translation_options!(options) if as_html if key.start_with?(i18n_scope + ".") translated = catch(:exception) do i18n_backend.translate(locale, key, options) end # Fallback to the global translations if translated.is_a? ::I18n::MissingTranslation return super(key, locale: locale, **options) end translated = html_safe_translation(translated) if as_html translated else super(key, locale: locale, **options) end end alias_method :t, :translate # Exposes .i18n_scope as an instance method def i18n_scope self.class.i18n_scope end private def html_safe_translation(translation) if translation.respond_to?(:map) translation.map { |element| html_safe_translation(element) } else # It's assumed here that objects loaded by the i18n backend will respond to `#html_safe?`. # It's reasonable that if we're in Rails, `active_support/core_ext/string/output_safety.rb` # will provide this to `Object`. translation.html_safe end end def html_escape_translation_options!(options) options.each do |name, value| next if ::I18n.reserved_keys_pattern.match?(name) next if name == :count && value.is_a?(Numeric) options[name] = ERB::Util.html_escape(value.to_s) end end end end view_component-3.8.0/lib/view_component/version.rb0000644000004100000410000000040414537061632022427 0ustar www-datawww-data# frozen_string_literal: true module ViewComponent module VERSION MAJOR = 3 MINOR = 8 PATCH = 0 PRE = nil STRING = [MAJOR, MINOR, PATCH, PRE].compact.join(".") end end puts ViewComponent::VERSION::STRING if __FILE__ == $PROGRAM_NAME view_component-3.8.0/lib/view_component/deprecation.rb0000644000004100000410000000032314537061632023237 0ustar www-datawww-data# frozen_string_literal: true require "active_support/deprecation" module ViewComponent DEPRECATION_HORIZON = "4.0.0" Deprecation = ActiveSupport::Deprecation.new(DEPRECATION_HORIZON, "ViewComponent") end view_component-3.8.0/lib/view_component/inline_template.rb0000644000004100000410000000302214537061632024112 0ustar www-datawww-data# frozen_string_literal: true module ViewComponent # :nodoc: module InlineTemplate extend ActiveSupport::Concern Template = Struct.new(:source, :language, :path, :lineno) class_methods do def method_missing(method, *args) return super if !method.end_with?("_template") if defined?(@__vc_inline_template_defined) && @__vc_inline_template_defined raise MultipleInlineTemplatesError end if args.size != 1 raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 1)" end ext = method.to_s.gsub("_template", "") template = args.first @__vc_inline_template_language = ext caller = caller_locations(1..1)[0] @__vc_inline_template = Template.new( template, ext, caller.absolute_path || caller.path, caller.lineno ) @__vc_inline_template_defined = true end ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true) def respond_to_missing?(method, include_all = false) method.end_with?("_template") || super end def inline_template @__vc_inline_template if defined?(@__vc_inline_template) end def inline_template_language @__vc_inline_template_language if defined?(@__vc_inline_template_language) end def inherited(subclass) super subclass.instance_variable_set(:@__vc_inline_template_language, inline_template_language) end end end end view_component-3.8.0/lib/view_component/test_helpers.rb0000644000004100000410000002201214537061632023442 0ustar www-datawww-data# frozen_string_literal: true module ViewComponent module TestHelpers begin require "capybara/minitest" include Capybara::Minitest::Assertions def page @page ||= Capybara::Node::Simple.new(rendered_content) end def refute_component_rendered assert_no_selector("body") end rescue LoadError # We don't have a test case for running an application without capybara installed. # It's probably fine to leave this without coverage. # :nocov: if ENV["DEBUG"] warn( "WARNING in `ViewComponent::TestHelpers`: Add `capybara` " \ "to Gemfile to use Capybara assertions." ) end # :nocov: end # Returns the result of a render_inline call. # # @return [ActionView::OutputBuffer] attr_reader :rendered_content # Render a component inline. Internally sets `page` to be a `Capybara::Node::Simple`, # allowing for Capybara assertions to be used: # # ```ruby # render_inline(MyComponent.new) # assert_text("Hello, World!") # ``` # # @param component [ViewComponent::Base, ViewComponent::Collection] The instance of the component to be rendered. # @return [Nokogiri::HTML] def render_inline(component, **args, &block) @page = nil @rendered_content = if Rails.version.to_f >= 6.1 vc_test_controller.view_context.render(component, args, &block) # :nocov: else vc_test_controller.view_context.render_component(component, &block) end # :nocov: Nokogiri::HTML.fragment(@rendered_content) end # Render a preview inline. Internally sets `page` to be a `Capybara::Node::Simple`, # allowing for Capybara assertions to be used: # # ```ruby # render_preview(:default) # assert_text("Hello, World!") # ``` # # Note: `#rendered_preview` expects a preview to be defined with the same class # name as the calling test, but with `Test` replaced with `Preview`: # # MyComponentTest -> MyComponentPreview etc. # # In RSpec, `Preview` is appended to `described_class`. # # @param name [String] The name of the preview to be rendered. # @param from [ViewComponent::Preview] The class of the preview to be rendered. # @param params [Hash] Parameters to be passed to the preview. # @return [Nokogiri::HTML] def render_preview(name, from: __vc_test_helpers_preview_class, params: {}) previews_controller = __vc_test_helpers_build_controller(Rails.application.config.view_component.preview_controller.constantize) # From what I can tell, it's not possible to overwrite all request parameters # at once, so we set them individually here. params.each do |k, v| previews_controller.request.params[k] = v end previews_controller.request.params[:path] = "#{from.preview_name}/#{name}" previews_controller.set_response!(ActionDispatch::Response.new) result = previews_controller.previews @rendered_content = result Nokogiri::HTML.fragment(@rendered_content) end # Execute the given block in the view context (using `instance_exec`). # Internally sets `page` to be a `Capybara::Node::Simple`, allowing for # Capybara assertions to be used. All arguments are forwarded to the block. # # ```ruby # render_in_view_context(arg1, arg2: nil) do |arg1, arg2:| # render(MyComponent.new(arg1, arg2)) # end # # assert_text("Hello, World!") # ``` def render_in_view_context(*args, &block) @page = nil @rendered_content = vc_test_controller.view_context.instance_exec(*args, &block) Nokogiri::HTML.fragment(@rendered_content) end ruby2_keywords(:render_in_view_context) if respond_to?(:ruby2_keywords, true) # Set the Action Pack request variant for the given block: # # ```ruby # with_variant(:phone) do # render_inline(MyComponent.new) # end # ``` # # @param variant [Symbol] The variant to be set for the provided block. def with_variant(variant) old_variants = vc_test_controller.view_context.lookup_context.variants vc_test_controller.view_context.lookup_context.variants = variant yield ensure vc_test_controller.view_context.lookup_context.variants = old_variants end # Set the controller to be used while executing the given block, # allowing access to controller-specific methods: # # ```ruby # with_controller_class(UsersController) do # render_inline(MyComponent.new) # end # ``` # # @param klass [ActionController::Base] The controller to be used. def with_controller_class(klass) old_controller = defined?(@vc_test_controller) && @vc_test_controller @vc_test_controller = __vc_test_helpers_build_controller(klass) yield ensure @vc_test_controller = old_controller end # Set the URL of the current request (such as when using request-dependent path helpers): # # ```ruby # with_request_url("/users/42") do # render_inline(MyComponent.new) # end # ``` # # To use a specific host, pass the host param: # # ```ruby # with_request_url("/users/42", host: "app.example.com") do # render_inline(MyComponent.new) # end # ``` # # To specify a request method, pass the method param: # # ```ruby # with_request_url("/users/42", method: "POST") do # render_inline(MyComponent.new) # end # ``` # # @param path [String] The path to set for the current request. # @param host [String] The host to set for the current request. # @param method [String] The request method to set for the current request. def with_request_url(full_path, host: nil, method: nil) old_request_host = vc_test_request.host old_request_method = vc_test_request.request_method old_request_path_info = vc_test_request.path_info old_request_path_parameters = vc_test_request.path_parameters old_request_query_parameters = vc_test_request.query_parameters old_request_query_string = vc_test_request.query_string old_controller = defined?(@vc_test_controller) && @vc_test_controller path, query = full_path.split("?", 2) vc_test_request.instance_variable_set(:@fullpath, full_path) vc_test_request.instance_variable_set(:@original_fullpath, full_path) vc_test_request.host = host if host vc_test_request.request_method = method if method vc_test_request.path_info = path vc_test_request.path_parameters = Rails.application.routes.recognize_path_with_request(vc_test_request, path, {}) vc_test_request.set_header("action_dispatch.request.query_parameters", Rack::Utils.parse_nested_query(query).with_indifferent_access) vc_test_request.set_header(Rack::QUERY_STRING, query) yield ensure vc_test_request.host = old_request_host vc_test_request.request_method = old_request_method vc_test_request.path_info = old_request_path_info vc_test_request.path_parameters = old_request_path_parameters vc_test_request.set_header("action_dispatch.request.query_parameters", old_request_query_parameters) vc_test_request.set_header(Rack::QUERY_STRING, old_request_query_string) @vc_test_controller = old_controller end # Access the controller used by `render_inline`: # # ```ruby # test "logged out user sees login link" do # vc_test_controller.expects(:logged_in?).at_least_once.returns(false) # render_inline(LoginComponent.new) # assert_selector("[aria-label='You must be signed in']") # end # ``` # # @return [ActionController::Base] def vc_test_controller @vc_test_controller ||= __vc_test_helpers_build_controller(Base.test_controller.constantize) end # Access the request used by `render_inline`: # # ```ruby # test "component does not render in Firefox" do # request.env["HTTP_USER_AGENT"] = "Mozilla/5.0" # render_inline(NoFirefoxComponent.new) # refute_component_rendered # end # ``` # # @return [ActionDispatch::TestRequest] def vc_test_request @vc_test_request ||= begin out = ActionDispatch::TestRequest.create out.session = ActionController::TestSession.new out end end # Note: We prefix private methods here to prevent collisions in consumer's tests. private def __vc_test_helpers_build_controller(klass) klass.new.tap { |c| c.request = vc_test_request }.extend(Rails.application.routes.url_helpers) end def __vc_test_helpers_preview_class result = if respond_to?(:described_class) raise "`render_preview` expected a described_class, but it is nil." if described_class.nil? "#{described_class}Preview" else self.class.name.gsub("Test", "Preview") end result = result.constantize rescue NameError raise NameError, "`render_preview` expected to find #{result}, but it does not exist." end end end view_component-3.8.0/lib/view_component/docs_builder_component.rb0000644000004100000410000000413114537061632025463 0ustar www-datawww-data# frozen_string_literal: true module ViewComponent class DocsBuilderComponent < Base class Section < Struct.new(:heading, :methods, :error_klasses, :show_types, keyword_init: true) def initialize(heading: nil, methods: [], error_klasses: [], show_types: true) methods.sort_by! { |method| method[:name] } error_klasses.sort! super end end class ErrorKlassDoc < ViewComponent::Base def initialize(error_klass, _show_types) @error_klass = error_klass end def klass_name @error_klass.gsub("ViewComponent::", "").gsub("::MESSAGE", "") end def error_message ViewComponent.const_get(@error_klass) end def call <<~DOCS.chomp `#{klass_name}` #{error_message} DOCS end end class MethodDoc < ViewComponent::Base def initialize(method, show_types = true) @method = method @show_types = show_types end def deprecated? @method.tag(:deprecated).present? end def suffix " (Deprecated)" if deprecated? end def types " → [#{@method.tag(:return).types.join(",")}]" if @method.tag(:return)&.types && @show_types end def signature_or_name @method.signature ? @method.signature.gsub("def ", "") : @method.name end def separator @method.sep end def docstring @method.docstring end def deprecation_text @method.tag(:deprecated)&.text end def docstring_and_deprecation_text <<~DOCS.strip #{docstring} #{"_#{deprecation_text}_" if deprecated?} DOCS end def call <<~DOCS.chomp `#{separator}#{signature_or_name}`#{types}#{suffix} #{docstring_and_deprecation_text} DOCS end end # { heading: String, public_only: Boolean, show_types: Boolean} def initialize(sections: []) @sections = sections end # deprecation # return # only public methods # sig with types or name end end view_component-3.8.0/lib/view_component/rendering_monkey_patch.rb0000644000004100000410000000043714537061632025466 0ustar www-datawww-data# frozen_string_literal: true module ViewComponent module RenderingMonkeyPatch # :nodoc: def render(options = {}, args = {}) if options.respond_to?(:render_in) self.response_body = options.render_in(view_context) else super end end end end view_component-3.8.0/lib/view_component/render_monkey_patch.rb0000644000004100000410000000041714537061632024766 0ustar www-datawww-data# frozen_string_literal: true module ViewComponent module RenderMonkeyPatch # :nodoc: def render(options = {}, args = {}, &block) if options.respond_to?(:render_in) options.render_in(self, &block) else super end end end end view_component-3.8.0/lib/view_component/slotable.rb0000644000004100000410000003335014537061632022555 0ustar www-datawww-data# frozen_string_literal: true require "active_support/concern" require "active_support/inflector/inflections" require "view_component/slot" module ViewComponent module Slotable extend ActiveSupport::Concern RESERVED_NAMES = { singular: %i[content render].freeze, plural: %i[contents renders].freeze }.freeze # Setup component slot state included do # Hash of registered Slots class_attribute :registered_slots self.registered_slots = {} end class_methods do ## # Registers a sub-component # # = Example # # renders_one :header -> (classes:) do # HeaderComponent.new(classes: classes) # end # # # OR # # renders_one :header, HeaderComponent # # where `HeaderComponent` is defined as: # # class HeaderComponent < ViewComponent::Base # def initialize(classes:) # @classes = classes # end # end # # and has the following template: # #
# <%= content %> #
# # = Rendering sub-component content # # The component's sidecar template can access the sub-component by calling a # helper method with the same name as the sub-component. # #

# <%= header do %> # My header title # <% end %> #

# # = Setting sub-component content # # Consumers of the component can render a sub-component by calling a # helper method with the same name as the slot prefixed with `with_`. # # <%= render_inline(MyComponent.new) do |component| %> # <% component.with_header(classes: "Foo") do %> #

Bar

# <% end %> # <% end %> # # Additionally, content can be set by calling `with_SLOT_NAME_content` # on the component instance. # # <%= render_inline(MyComponent.new.with_header_content("Foo")) %> def renders_one(slot_name, callable = nil) validate_singular_slot_name(slot_name) if callable.is_a?(Hash) && callable.key?(:types) register_polymorphic_slot(slot_name, callable[:types], collection: false) else validate_plural_slot_name(ActiveSupport::Inflector.pluralize(slot_name).to_sym) setter_method_name = :"with_#{slot_name}" define_method setter_method_name do |*args, &block| set_slot(slot_name, nil, *args, &block) end ruby2_keywords(setter_method_name) if respond_to?(:ruby2_keywords, true) define_method slot_name do get_slot(slot_name) end define_method "#{slot_name}?" do get_slot(slot_name).present? end define_method "with_#{slot_name}_content" do |content| send(setter_method_name) { content.to_s } self end register_slot(slot_name, collection: false, callable: callable) end end ## # Registers a collection sub-component # # = Example # # renders_many :items, -> (name:) { ItemComponent.new(name: name } # # # OR # # renders_many :items, ItemComponent # # = Rendering sub-components # # The component's sidecar template can access the slot by calling a # helper method with the same name as the slot. # #

# <% items.each do |item| %> # <%= item %> # <% end %> #

# # = Setting sub-component content # # Consumers of the component can set the content of a slot by calling a # helper method with the same name as the slot prefixed with `with_`. The # method can be called multiple times to append to the slot. # # <%= render_inline(MyComponent.new) do |component| %> # <% component.with_item(name: "Foo") do %> #

One

# <% end %> # # <% component.with_item(name: "Bar") do %> #

two

# <% end %> # <% end %> def renders_many(slot_name, callable = nil) validate_plural_slot_name(slot_name) if callable.is_a?(Hash) && callable.key?(:types) register_polymorphic_slot(slot_name, callable[:types], collection: true) else singular_name = ActiveSupport::Inflector.singularize(slot_name) validate_singular_slot_name(ActiveSupport::Inflector.singularize(slot_name).to_sym) setter_method_name = :"with_#{singular_name}" define_method setter_method_name do |*args, &block| set_slot(slot_name, nil, *args, &block) end ruby2_keywords(setter_method_name) if respond_to?(:ruby2_keywords, true) define_method "with_#{singular_name}_content" do |content| send(setter_method_name) { content.to_s } self end define_method :"with_#{slot_name}" do |collection_args = nil, &block| collection_args.map do |args| if args.respond_to?(:to_hash) set_slot(slot_name, nil, **args, &block) else set_slot(slot_name, nil, *args, &block) end end end define_method slot_name do get_slot(slot_name) end define_method "#{slot_name}?" do get_slot(slot_name).present? end register_slot(slot_name, collection: true, callable: callable) end end def slot_type(slot_name) registered_slot = registered_slots[slot_name] if registered_slot registered_slot[:collection] ? :collection : :single else plural_slot_name = ActiveSupport::Inflector.pluralize(slot_name).to_sym plural_registered_slot = registered_slots[plural_slot_name] plural_registered_slot&.fetch(:collection) ? :collection_item : nil end end # Clone slot configuration into child class # see #test_slots_pollution def inherited(child) child.registered_slots = registered_slots.clone super end def register_polymorphic_slot(slot_name, types, collection:) define_method(slot_name) do get_slot(slot_name) end define_method("#{slot_name}?") do get_slot(slot_name).present? end renderable_hash = types.each_with_object({}) do |(poly_type, poly_attributes_or_callable), memo| if poly_attributes_or_callable.is_a?(Hash) poly_callable = poly_attributes_or_callable[:renders] poly_slot_name = poly_attributes_or_callable[:as] else poly_callable = poly_attributes_or_callable poly_slot_name = nil end poly_slot_name ||= if collection "#{ActiveSupport::Inflector.singularize(slot_name)}_#{poly_type}" else "#{slot_name}_#{poly_type}" end memo[poly_type] = define_slot( poly_slot_name, collection: collection, callable: poly_callable ) setter_method_name = :"with_#{poly_slot_name}" if instance_methods.include?(setter_method_name) raise AlreadyDefinedPolymorphicSlotSetterError.new(setter_method_name, poly_slot_name) end define_method(setter_method_name) do |*args, &block| set_polymorphic_slot(slot_name, poly_type, *args, &block) end ruby2_keywords(setter_method_name) if respond_to?(:ruby2_keywords, true) define_method "with_#{poly_slot_name}_content" do |content| send(setter_method_name) { content.to_s } self end end registered_slots[slot_name] = { collection: collection, renderable_hash: renderable_hash } end private def register_slot(slot_name, **kwargs) registered_slots[slot_name] = define_slot(slot_name, **kwargs) end def define_slot(slot_name, collection:, callable:) # Setup basic slot data slot = { collection: collection } return slot unless callable # If callable responds to `render_in`, we set it on the slot as a renderable if callable.respond_to?(:method_defined?) && callable.method_defined?(:render_in) slot[:renderable] = callable elsif callable.is_a?(String) # If callable is a string, we assume it's referencing an internal class slot[:renderable_class_name] = callable elsif callable.respond_to?(:call) # If slot doesn't respond to `render_in`, we assume it's a proc, # define a method, and save a reference to it to call when setting method_name = :"_call_#{slot_name}" define_method method_name, &callable slot[:renderable_function] = instance_method(method_name) else raise(InvalidSlotDefinitionError) end slot end def validate_plural_slot_name(slot_name) if RESERVED_NAMES[:plural].include?(slot_name.to_sym) raise ReservedPluralSlotNameError.new(name, slot_name) end raise_if_slot_name_uncountable(slot_name) raise_if_slot_conflicts_with_call(slot_name) raise_if_slot_ends_with_question_mark(slot_name) raise_if_slot_registered(slot_name) end def validate_singular_slot_name(slot_name) if slot_name.to_sym == :content raise ContentSlotNameError.new(name) end if RESERVED_NAMES[:singular].include?(slot_name.to_sym) raise ReservedSingularSlotNameError.new(name, slot_name) end raise_if_slot_conflicts_with_call(slot_name) raise_if_slot_ends_with_question_mark(slot_name) raise_if_slot_registered(slot_name) end def raise_if_slot_registered(slot_name) if registered_slots.key?(slot_name) # TODO remove? This breaks overriding slots when slots are inherited raise RedefinedSlotError.new(name, slot_name) end end def raise_if_slot_ends_with_question_mark(slot_name) raise SlotPredicateNameError.new(name, slot_name) if slot_name.to_s.end_with?("?") end def raise_if_slot_conflicts_with_call(slot_name) if slot_name.start_with?("call_") raise InvalidSlotNameError, "Slot cannot start with 'call_'. Please rename #{slot_name}" end end def raise_if_slot_name_uncountable(slot_name) slot_name = slot_name.to_s if slot_name.pluralize == slot_name.singularize raise UncountableSlotNameError.new(name, slot_name) end end end def get_slot(slot_name) content unless content_evaluated? # ensure content is loaded so slots will be defined slot = self.class.registered_slots[slot_name] @__vc_set_slots ||= {} if @__vc_set_slots[slot_name] return @__vc_set_slots[slot_name] end if slot[:collection] [] end end def set_slot(slot_name, slot_definition = nil, *args, &block) slot_definition ||= self.class.registered_slots[slot_name] slot = Slot.new(self) # Passing the block to the sub-component wrapper like this has two # benefits: # # 1. If this is a `content_area` style sub-component, we will render the # block via the `slot` # # 2. Since we've to pass block content to components when calling # `render`, evaluating the block here would require us to call # `view_context.capture` twice, which is slower slot.__vc_content_block = block if block # If class if slot_definition[:renderable] slot.__vc_component_instance = slot_definition[:renderable].new(*args) # If class name as a string elsif slot_definition[:renderable_class_name] slot.__vc_component_instance = self.class.const_get(slot_definition[:renderable_class_name]).new(*args) # If passed a lambda elsif slot_definition[:renderable_function] # Use `bind(self)` to ensure lambda is executed in the context of the # current component. This is necessary to allow the lambda to access helper # methods like `content_tag` as well as parent component state. renderable_function = slot_definition[:renderable_function].bind(self) renderable_value = if block renderable_function.call(*args) do |*rargs| view_context.capture(*rargs, &block) end else renderable_function.call(*args) end # Function calls can return components, so if it's a component handle it specially if renderable_value.respond_to?(:render_in) slot.__vc_component_instance = renderable_value else slot.__vc_content = renderable_value end end @__vc_set_slots ||= {} if slot_definition[:collection] @__vc_set_slots[slot_name] ||= [] @__vc_set_slots[slot_name].push(slot) else @__vc_set_slots[slot_name] = slot end slot end ruby2_keywords(:set_slot) if respond_to?(:ruby2_keywords, true) def set_polymorphic_slot(slot_name, poly_type = nil, *args, &block) slot_definition = self.class.registered_slots[slot_name] if !slot_definition[:collection] && (defined?(@__vc_set_slots) && @__vc_set_slots[slot_name]) raise ContentAlreadySetForPolymorphicSlotError.new(slot_name) end poly_def = slot_definition[:renderable_hash][poly_type] set_slot(slot_name, poly_def, *args, &block) end ruby2_keywords(:set_polymorphic_slot) if respond_to?(:ruby2_keywords, true) end end view_component-3.8.0/lib/view_component/use_helpers.rb0000644000004100000410000000104514537061632023262 0ustar www-datawww-data# frozen_string_literal: true module ViewComponent::UseHelpers extend ActiveSupport::Concern class_methods do def use_helpers(*args) args.each do |helper_method| class_eval(<<-RUBY, __FILE__, __LINE__ + 1) def #{helper_method}(*args, &block) raise HelpersCalledBeforeRenderError if view_context.nil? __vc_original_view_context.#{helper_method}(*args, &block) end RUBY ruby2_keywords(helper_method) if respond_to?(:ruby2_keywords, true) end end end end view_component-3.8.0/lib/view_component/with_content_helper.rb0000644000004100000410000000035014537061632025006 0ustar www-datawww-data# frozen_string_literal: true module ViewComponent module WithContentHelper def with_content(value) raise NilWithContentError if value.nil? @__vc_content_set_by_with_content = value self end end end view_component-3.8.0/lib/view_component/render_component_to_string_helper.rb0000644000004100000410000000031514537061632027733 0ustar www-datawww-data# frozen_string_literal: true module ViewComponent module RenderComponentToStringHelper # :nodoc: def render_component_to_string(component) component.render_in(view_context) end end end view_component-3.8.0/lib/view_component/slot.rb0000644000004100000410000000655114537061632021734 0ustar www-datawww-data# frozen_string_literal: true require "view_component/with_content_helper" module ViewComponent class Slot include ViewComponent::WithContentHelper attr_writer :__vc_component_instance, :__vc_content_block, :__vc_content def initialize(parent) @parent = parent end def content? return true if defined?(@__vc_content) && @__vc_content.present? return true if defined?(@__vc_content_set_by_with_content) && @__vc_content_set_by_with_content.present? return true if defined?(@__vc_content_block) && @__vc_content_block.present? return false if !__vc_component_instance? @__vc_component_instance.content? end def with_content(args) if __vc_component_instance? @__vc_component_instance.with_content(args) else super end end # Used to render the slot content in the template # # There's currently 3 different values that may be set, that we can render. # # If the slot renderable is a component, the string class name of a # component, or a function that returns a component, we render that # component instance, returning the string. # # If the slot renderable is a function and returns a string, it's # set as `@__vc_content` and is returned directly. # # If there is no slot renderable, we evaluate the block passed to # the slot and return it. def to_s return @content if defined?(@content) view_context = @parent.send(:view_context) if defined?(@__vc_content_block) && defined?(@__vc_content_set_by_with_content) raise DuplicateSlotContentError.new(self.class.name) end @content = if __vc_component_instance? @__vc_component_instance.__vc_original_view_context = @parent.__vc_original_view_context if defined?(@__vc_content_block) # render_in is faster than `parent.render` @__vc_component_instance.render_in(view_context, &@__vc_content_block) else @__vc_component_instance.render_in(view_context) end elsif defined?(@__vc_content) @__vc_content elsif defined?(@__vc_content_block) view_context.capture(&@__vc_content_block) elsif defined?(@__vc_content_set_by_with_content) @__vc_content_set_by_with_content end @content = @content.to_s end # Allow access to public component methods via the wrapper # # for example # # calling `header.name` (where `header` is a slot) will call `name` # on the `HeaderComponent` instance. # # Where the component may look like: # # class MyComponent < ViewComponent::Base # has_one :header, HeaderComponent # # class HeaderComponent < ViewComponent::Base # def name # @name # end # end # end # def method_missing(symbol, *args, &block) @__vc_component_instance.public_send(symbol, *args, &block) end ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true) def html_safe? # :nocov: to_s.html_safe? # :nocov: end def respond_to_missing?(symbol, include_all = false) __vc_component_instance? && @__vc_component_instance.respond_to?(symbol, include_all) end private def __vc_component_instance? defined?(@__vc_component_instance) end end end view_component-3.8.0/lib/view_component/render_to_string_monkey_patch.rb0000644000004100000410000000043114537061632027052 0ustar www-datawww-data# frozen_string_literal: true module ViewComponent module RenderToStringMonkeyPatch # :nodoc: def render_to_string(options = {}, args = {}) if options.respond_to?(:render_in) options.render_in(view_context) else super end end end end view_component-3.8.0/lib/view_component/errors.rb0000644000004100000410000002146614537061632022271 0ustar www-datawww-datamodule ViewComponent class BaseError < StandardError def initialize super(self.class::MESSAGE) end end class DuplicateSlotContentError < StandardError MESSAGE = "It looks like a block was provided after calling `with_content` on COMPONENT, " \ "which means that ViewComponent doesn't know which content to use.\n\n" \ "To fix this issue, use either `with_content` or a block." def initialize(klass_name) super(MESSAGE.gsub("COMPONENT", klass_name.to_s)) end end class TemplateError < StandardError def initialize(errors) super(errors.join(", ")) end end class MultipleInlineTemplatesError < BaseError MESSAGE = "Inline templates can only be defined once per-component." end class MissingPreviewTemplateError < StandardError MESSAGE = "A preview template for example EXAMPLE doesn't exist.\n\n" \ "To fix this issue, create a template for the example." def initialize(example) super(MESSAGE.gsub("EXAMPLE", example)) end end class DuplicateContentError < StandardError MESSAGE = "It looks like a block was provided after calling `with_content` on COMPONENT, " \ "which means that ViewComponent doesn't know which content to use.\n\n" \ "To fix this issue, use either `with_content` or a block." def initialize(klass_name) super(MESSAGE.gsub("COMPONENT", klass_name.to_s)) end end class EmptyOrInvalidInitializerError < StandardError MESSAGE = "The COMPONENT initializer is empty or invalid. " \ "It must accept the parameter `PARAMETER` to render it as a collection.\n\n" \ "To fix this issue, update the initializer to accept `PARAMETER`.\n\n" \ "See [the collections docs](https://viewcomponent.org/guide/collections.html) for more information on rendering collections." def initialize(klass_name, parameter) super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("PARAMETER", parameter.to_s)) end end class MissingCollectionArgumentError < StandardError MESSAGE = "The initializer for COMPONENT doesn't accept the parameter `PARAMETER`, " \ "which is required to render it as a collection.\n\n" \ "To fix this issue, update the initializer to accept `PARAMETER`.\n\n" \ "See [the collections docs](https://viewcomponent.org/guide/collections.html) for more information on rendering collections." def initialize(klass_name, parameter) super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("PARAMETER", parameter.to_s)) end end class ReservedParameterError < StandardError MESSAGE = "COMPONENT initializer can't accept the parameter `PARAMETER`, as it will override a " \ "public ViewComponent method. To fix this issue, rename the parameter." def initialize(klass_name, parameter) super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("PARAMETER", parameter.to_s)) end end class InvalidCollectionArgumentError < BaseError MESSAGE = "The value of the first argument passed to `with_collection` isn't a valid collection. " \ "Make sure it responds to `to_ary`." end class ContentSlotNameError < StandardError MESSAGE = "COMPONENT declares a slot named content, which is a reserved word in ViewComponent.\n\n" \ "Content passed to a ViewComponent as a block is captured and assigned to the `content` accessor without having to create an explicit slot.\n\n" \ "To fix this issue, either use the `content` accessor directly or choose a different slot name." def initialize(klass_name) super(MESSAGE.gsub("COMPONENT", klass_name.to_s)) end end class InvalidSlotDefinitionError < BaseError MESSAGE = "Invalid slot definition. Please pass a class, " \ "string, or callable (that is proc, lambda, etc)" end class InvalidSlotNameError < StandardError end class SlotPredicateNameError < InvalidSlotNameError MESSAGE = "COMPONENT declares a slot named SLOT_NAME, which ends with a question mark.\n\n" \ "This isn't allowed because the ViewComponent framework already provides predicate " \ "methods ending in `?`.\n\n" \ "To fix this issue, choose a different name." def initialize(klass_name, slot_name) super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s)) end end class RedefinedSlotError < StandardError MESSAGE = "COMPONENT declares the SLOT_NAME slot multiple times.\n\n" \ "To fix this issue, choose a different slot name." def initialize(klass_name, slot_name) super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s)) end end class ReservedSingularSlotNameError < InvalidSlotNameError MESSAGE = "COMPONENT declares a slot named SLOT_NAME, which is a reserved word in the ViewComponent framework.\n\n" \ "To fix this issue, choose a different name." def initialize(klass_name, slot_name) super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s)) end end class ReservedPluralSlotNameError < InvalidSlotNameError MESSAGE = "COMPONENT declares a slot named SLOT_NAME, which is a reserved word in the ViewComponent framework.\n\n" \ "To fix this issue, choose a different name." def initialize(klass_name, slot_name) super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s)) end end class UncountableSlotNameError < InvalidSlotNameError MESSAGE = "COMPONENT declares a slot named SLOT_NAME, which is an uncountable word\n\n" \ "To fix this issue, choose a different name." def initialize(klass_name, slot_name) super(MESSAGE.gsub("COMPONENT", klass_name.to_s).gsub("SLOT_NAME", slot_name.to_s)) end end class ContentAlreadySetForPolymorphicSlotError < StandardError MESSAGE = "Content for slot SLOT_NAME has already been provided." def initialize(slot_name) super(MESSAGE.gsub("SLOT_NAME", slot_name.to_s)) end end class NilWithContentError < BaseError MESSAGE = "No content provided to `#with_content` for #{self}.\n\n" \ "To fix this issue, pass a value." end class TranslateCalledBeforeRenderError < BaseError MESSAGE = "`#translate` can't be used during initialization as it depends " \ "on the view context that only exists once a ViewComponent is passed to " \ "the Rails render pipeline.\n\n" \ "It's sometimes possible to fix this issue by moving code dependent on " \ "`#translate` to a [`#before_render` method](https://viewcomponent.org/api.html#before_render--void)." end class HelpersCalledBeforeRenderError < BaseError MESSAGE = "`#helpers` can't be used during initialization as it depends " \ "on the view context that only exists once a ViewComponent is passed to " \ "the Rails render pipeline.\n\n" \ "It's sometimes possible to fix this issue by moving code dependent on " \ "`#helpers` to a [`#before_render` method](https://viewcomponent.org/api.html#before_render--void)." end class ControllerCalledBeforeRenderError < BaseError MESSAGE = "`#controller` can't be used during initialization, as it depends " \ "on the view context that only exists once a ViewComponent is passed to " \ "the Rails render pipeline.\n\n" \ "It's sometimes possible to fix this issue by moving code dependent on " \ "`#controller` to a [`#before_render` method](https://viewcomponent.org/api.html#before_render--void)." end # :nocov: class NoMatchingTemplatesForPreviewError < StandardError MESSAGE = "Found 0 matches for templates for TEMPLATE_IDENTIFIER." def initialize(template_identifier) super(MESSAGE.gsub("TEMPLATE_IDENTIFIER", template_identifier)) end end class MultipleMatchingTemplatesForPreviewError < StandardError MESSAGE = "Found multiple templates for TEMPLATE_IDENTIFIER." def initialize(template_identifier) super(MESSAGE.gsub("TEMPLATE_IDENTIFIER", template_identifier)) end end # :nocov: class SystemTestControllerOnlyAllowedInTestError < BaseError MESSAGE = "ViewComponent SystemTest controller must only be called in a test environment for security reasons." end class SystemTestControllerNefariousPathError < BaseError MESSAGE = "ViewComponent SystemTest controller attempted to load a file outside of the expected directory." end class AlreadyDefinedPolymorphicSlotSetterError < StandardError MESSAGE = "A method called 'SETTER_METHOD_NAME' already exists and would be overwritten by the 'SETTER_NAME' polymorphic " \ "slot setter.\n\nPlease choose a different setter name." def initialize(setter_method_name, setter_name) super(MESSAGE.gsub("SETTER_METHOD_NAME", setter_method_name.to_s).gsub("SETTER_NAME", setter_name.to_s)) end end end view_component-3.8.0/lib/view_component/system_test_case.rb0000644000004100000410000000037414537061632024326 0ustar www-datawww-data# frozen_string_literal: true require "active_support/test_case" module ViewComponent class SystemTestCase < ActionDispatch::SystemTestCase include ViewComponent::SystemTestHelpers def page Capybara.current_session end end end view_component-3.8.0/lib/view_component/base.rb0000644000004100000410000005232314537061632021663 0ustar www-datawww-data# frozen_string_literal: true require "action_view" require "active_support/configurable" require "view_component/collection" require "view_component/compile_cache" require "view_component/compiler" require "view_component/config" require "view_component/errors" require "view_component/inline_template" require "view_component/preview" require "view_component/slotable" require "view_component/translatable" require "view_component/with_content_helper" require "view_component/use_helpers" module ViewComponent class Base < ActionView::Base class << self delegate(*ViewComponent::Config.defaults.keys, to: :config) # Returns the current config. # # @return [ActiveSupport::OrderedOptions] def config ViewComponent::Config.current end end include ViewComponent::InlineTemplate include ViewComponent::Slotable include ViewComponent::Translatable include ViewComponent::WithContentHelper RESERVED_PARAMETER = :content # For CSRF authenticity tokens in forms delegate :form_authenticity_token, :protect_against_forgery?, :config, to: :helpers # For Content Security Policy nonces delegate :content_security_policy_nonce, to: :helpers # Config option that strips trailing whitespace in templates before compiling them. class_attribute :__vc_strip_trailing_whitespace, instance_accessor: false, instance_predicate: false self.__vc_strip_trailing_whitespace = false # class_attribute:default doesn't work until Rails 5.2 attr_accessor :__vc_original_view_context # Components render in their own view context. Helpers and other functionality # require a reference to the original Rails view context, an instance of # `ActionView::Base`. Use this method to set a reference to the original # view context. Objects that implement this method will render in the component's # view context, while objects that don't will render in the original view context # so helpers, etc work as expected. # # @param view_context [ActionView::Base] The original view context. # @return [void] def set_original_view_context(view_context) self.__vc_original_view_context = view_context end # Entrypoint for rendering components. # # - `view_context`: ActionView context from calling view # - `block`: optional block to be captured within the view context # # Returns HTML that has been escaped by the respective template handler. # # @return [String] def render_in(view_context, &block) self.class.compile(raise_errors: true) @view_context = view_context self.__vc_original_view_context ||= view_context @output_buffer = ActionView::OutputBuffer.new @lookup_context ||= view_context.lookup_context # required for path helpers in older Rails versions @view_renderer ||= view_context.view_renderer # For content_for @view_flow ||= view_context.view_flow # For i18n @virtual_path ||= virtual_path # For template variants (+phone, +desktop, etc.) @__vc_variant ||= @lookup_context.variants.first # For caching, such as #cache_if @current_template = nil unless defined?(@current_template) old_current_template = @current_template @current_template = self if block && defined?(@__vc_content_set_by_with_content) raise DuplicateContentError.new(self.class.name) end @__vc_content_evaluated = false @__vc_render_in_block = block before_render if render? render_template_for(@__vc_variant).to_s + output_postamble else "" end ensure @current_template = old_current_template end # Subclass components that call `super` inside their template code will cause a # double render if they emit the result. # # ```erb # <%= super %> # double-renders # <% super %> # doesn't double-render # ``` # # `super` also doesn't consider the current variant. `render_parent` renders the # parent template considering the current variant and emits the result without # double-rendering. def render_parent render_parent_to_string nil end # Renders the parent component to a string and returns it. This method is meant # to be used inside custom #call methods when a string result is desired, eg. # # ```ruby # def call # "
#{render_parent_to_string}
" # end # ``` # # When rendering the parent inside an .erb template, use `#render_parent` instead. def render_parent_to_string @__vc_parent_render_level ||= 0 # ensure a good starting value begin target_render = self.class.instance_variable_get(:@__vc_ancestor_calls)[@__vc_parent_render_level] @__vc_parent_render_level += 1 target_render.bind_call(self, @__vc_variant) ensure @__vc_parent_render_level -= 1 end end # Optional content to be returned after the rendered template. # # @return [String] def output_postamble "" end # Called before rendering the component. Override to perform operations that # depend on having access to the view context, such as helpers. # # @return [void] def before_render # noop end # Override to determine whether the ViewComponent should render. # # @return [Boolean] def render? true end # @private def initialize(*) end # Re-use original view_context if we're not rendering a component. # # This prevents an exception when rendering a partial inside of a component that has also been rendered outside # of the component. This is due to the partials compiled template method existing in the parent `view_context`, # and not the component's `view_context`. # # @private def render(options = {}, args = {}, &block) if options.respond_to?(:set_original_view_context) options.set_original_view_context(self.__vc_original_view_context) super else __vc_original_view_context.render(options, args, &block) end end # The current controller. Use sparingly as doing so introduces coupling # that inhibits encapsulation & reuse, often making testing difficult. # # @return [ActionController::Base] def controller raise ControllerCalledBeforeRenderError if view_context.nil? @__vc_controller ||= view_context.controller end # A proxy through which to access helpers. Use sparingly as doing so introduces # coupling that inhibits encapsulation & reuse, often making testing difficult. # # @return [ActionView::Base] def helpers raise HelpersCalledBeforeRenderError if view_context.nil? # Attempt to re-use the original view_context passed to the first # component rendered in the rendering pipeline. This prevents the # instantiation of a new view_context via `controller.view_context` which # always returns a new instance of the view context class. # # This allows ivars to remain persisted when using the same helper via # `helpers` across multiple components and partials. @__vc_helpers ||= __vc_original_view_context || controller.view_context end if ::Rails.env.development? || ::Rails.env.test? def method_missing(method_name, *args) # rubocop:disable Style/MissingRespondToMissing super rescue => e # rubocop:disable Style/RescueStandardError e.set_backtrace e.backtrace.tap(&:shift) raise e, <<~MESSAGE.chomp if view_context && e.is_a?(NameError) && helpers.respond_to?(method_name) #{e.message} You may be trying to call a method provided as a view helper. Did you mean `helpers.#{method_name}'? MESSAGE raise end end # Exposes .virtual_path as an instance method # # @private def virtual_path self.class.virtual_path end # For caching, such as #cache_if # @private def view_cache_dependencies [] end # For caching, such as #cache_if # # @private def format @__vc_variant if defined?(@__vc_variant) end # The current request. Use sparingly as doing so introduces coupling that # inhibits encapsulation & reuse, often making testing difficult. # # @return [ActionDispatch::Request] def request @request ||= controller.request if controller.respond_to?(:request) end # The content passed to the component instance as a block. # # @return [String] def content @__vc_content_evaluated = true return @__vc_content if defined?(@__vc_content) @__vc_content = if __vc_render_in_block_provided? view_context.capture(self, &@__vc_render_in_block) elsif __vc_content_set_by_with_content_defined? @__vc_content_set_by_with_content end end # Whether `content` has been passed to the component. # # @return [Boolean] def content? __vc_render_in_block_provided? || __vc_content_set_by_with_content_defined? end private attr_reader :view_context def __vc_render_in_block_provided? defined?(@view_context) && @view_context && @__vc_render_in_block end def __vc_content_set_by_with_content_defined? defined?(@__vc_content_set_by_with_content) end def content_evaluated? defined?(@__vc_content_evaluated) && @__vc_content_evaluated end # Set the controller used for testing components: # # ```ruby # config.view_component.test_controller = "MyTestController" # ``` # # Defaults to `nil`. If this is falsy, `"ApplicationController"` is used. Can also be # configured on a per-test basis using `with_controller_class`. # # Set if render monkey patches should be included or not in Rails <6.1: # # ```ruby # config.view_component.render_monkey_patch_enabled = false # ``` # # Path for component files # # ```ruby # config.view_component.view_component_path = "app/my_components" # ``` # # Defaults to `nil`. If this is falsy, `app/components` is used. # # Parent class for generated components # # ```ruby # config.view_component.component_parent_class = "MyBaseComponent" # ``` # # Defaults to nil. If this is falsy, generators will use # "ApplicationComponent" if defined, "ViewComponent::Base" otherwise. # # Configuration for generators. # # All options under this namespace default to `false` unless otherwise # stated. # # #### #sidecar # # Always generate a component with a sidecar directory: # # ```ruby # config.view_component.generate.sidecar = true # ``` # # #### #stimulus_controller # # Always generate a Stimulus controller alongside the component: # # ```ruby # config.view_component.generate.stimulus_controller = true # ``` # # #### #locale # # Always generate translations file alongside the component: # # ```ruby # config.view_component.generate.locale = true # ``` # # #### #distinct_locale_files # # Always generate as many translations files as available locales: # # ```ruby # config.view_component.generate.distinct_locale_files = true # ``` # # One file will be generated for each configured `I18n.available_locales`, # falling back to `[:en]` when no `available_locales` is defined. # # #### #preview # # Always generate preview alongside the component: # # ```ruby # config.view_component.generate.preview = true # ``` # # Defaults to `false`. class << self # @private attr_accessor :source_location, :virtual_path # Find sidecar files for the given extensions. # # The provided array of extensions is expected to contain # strings starting without the dot, example: `["erb", "haml"]`. # # For example, one might collect sidecar CSS files that need to be compiled. # @param extensions [Array] Extensions of which to return matching sidecar files. def sidecar_files(extensions) return [] unless source_location extensions = extensions.join(",") # view files in a directory named like the component directory = File.dirname(source_location) filename = File.basename(source_location, ".rb") component_name = name.demodulize.underscore # Add support for nested components defined in the same file. # # for example # # class MyComponent < ViewComponent::Base # class MyOtherComponent < ViewComponent::Base # end # end # # Without this, `MyOtherComponent` will not look for `my_component/my_other_component.html.erb` nested_component_files = if name.include?("::") && component_name != filename Dir["#{directory}/#{filename}/#{component_name}.*{#{extensions}}"] else [] end # view files in the same directory as the component sidecar_files = Dir["#{directory}/#{component_name}.*{#{extensions}}"] sidecar_directory_files = Dir["#{directory}/#{component_name}/#{filename}.*{#{extensions}}"] (sidecar_files - [source_location] + sidecar_directory_files + nested_component_files).uniq end # Render a component for each element in a collection ([documentation](/guide/collections)): # # ```ruby # render(ProductsComponent.with_collection(@products, foo: :bar)) # ``` # # @param collection [Enumerable] A list of items to pass the ViewComponent one at a time. # @param args [Arguments] Arguments to pass to the ViewComponent every time. def with_collection(collection, **args) Collection.new(self, collection, **args) end # Provide identifier for ActionView template annotations # # @private def short_identifier @short_identifier ||= defined?(Rails.root) ? source_location.sub("#{Rails.root}/", "") : source_location end # @private def inherited(child) # Compile so child will inherit compiled `call_*` template methods that # `compile` defines compile # Give the child its own personal #render_template_for to protect against the case when # eager loading is disabled and the parent component is rendered before the child. In # such a scenario, the parent will override ViewComponent::Base#render_template_for, # meaning it will not be called for any children and thus not compile their templates. if !child.instance_methods(false).include?(:render_template_for) && !child.compiled? child.class_eval <<~RUBY, __FILE__, __LINE__ + 1 def render_template_for(variant = nil) # Force compilation here so the compiler always redefines render_template_for. # This is mostly a safeguard to prevent infinite recursion. self.class.compile(raise_errors: true, force: true) # .compile replaces this method; call the new one render_template_for(variant) end RUBY end # If Rails application is loaded, add application url_helpers to the component context # we need to check this to use this gem as a dependency if defined?(Rails) && Rails.application && !(child < Rails.application.routes.url_helpers) child.include Rails.application.routes.url_helpers end # Derive the source location of the component Ruby file from the call stack. # We need to ignore `inherited` frames here as they indicate that `inherited` # has been re-defined by the consuming application, likely in ApplicationComponent. child.source_location = caller_locations(1, 10).reject { |l| l.label == "inherited" }[0].path # If Rails application is loaded, removes the first part of the path and the extension. if defined?(Rails) && Rails.application child.virtual_path = child.source_location.gsub( /(.*#{Regexp.quote(ViewComponent::Base.config.view_component_path)})|(\.rb)/, "" ) end # Set collection parameter to the extended component child.with_collection_parameter provided_collection_parameter if instance_methods(false).include?(:render_template_for) vc_ancestor_calls = defined?(@__vc_ancestor_calls) ? @__vc_ancestor_calls.dup : [] vc_ancestor_calls.unshift(instance_method(:render_template_for)) child.instance_variable_set(:@__vc_ancestor_calls, vc_ancestor_calls) end super end # @private def compiled? compiler.compiled? end # @private def ensure_compiled compile unless compiled? end # Compile templates to instance methods, assuming they haven't been compiled already. # # Do as much work as possible in this step, as doing so reduces the amount # of work done each time a component is rendered. # @private def compile(raise_errors: false, force: false) compiler.compile(raise_errors: raise_errors, force: force) end # @private def compiler @__vc_compiler ||= Compiler.new(self) end # we'll eventually want to update this to support other types # @private def type "text/html" end # @private def format :html end # @private def identifier source_location end # Set the parameter name used when rendering elements of a collection ([documentation](/guide/collections)): # # ```ruby # with_collection_parameter :item # ``` # # @param parameter [Symbol] The parameter name used when rendering elements of a collection. def with_collection_parameter(parameter) @provided_collection_parameter = parameter @initialize_parameters = nil end # Strips trailing whitespace from templates before compiling them. # # ```ruby # class MyComponent < ViewComponent::Base # strip_trailing_whitespace # end # ``` # # @param value [Boolean] Whether to strip newlines. def strip_trailing_whitespace(value = true) self.__vc_strip_trailing_whitespace = value end # Whether trailing whitespace will be stripped before compilation. # # @return [Boolean] def strip_trailing_whitespace? __vc_strip_trailing_whitespace end # Ensure the component initializer accepts the # collection parameter. By default, we don't # validate that the default parameter name # is accepted, as support for collection # rendering is optional. # @private TODO: add documentation def validate_collection_parameter!(validate_default: false) parameter = validate_default ? collection_parameter : provided_collection_parameter return unless parameter return if initialize_parameter_names.include?(parameter) || splatted_keyword_argument_present? # If Ruby can't parse the component class, then the initialize # parameters will be empty and ViewComponent will not be able to render # the component. if initialize_parameters.empty? raise EmptyOrInvalidInitializerError.new(name, parameter) end raise MissingCollectionArgumentError.new(name, parameter) end # Ensure the component initializer doesn't define # invalid parameters that could override the framework's # methods. # @private TODO: add documentation def validate_initialization_parameters! return unless initialize_parameter_names.include?(RESERVED_PARAMETER) raise ReservedParameterError.new(name, RESERVED_PARAMETER) end # @private def collection_parameter provided_collection_parameter || name && name.demodulize.underscore.chomp("_component").to_sym end # @private def collection_counter_parameter "#{collection_parameter}_counter".to_sym end # @private def counter_argument_present? initialize_parameter_names.include?(collection_counter_parameter) end # @private def collection_iteration_parameter "#{collection_parameter}_iteration".to_sym end # @private def iteration_argument_present? initialize_parameter_names.include?(collection_iteration_parameter) end private def splatted_keyword_argument_present? initialize_parameters.flatten.include?(:keyrest) && !initialize_parameters.include?([:keyrest, :**]) # Un-named splatted keyword args don't count! end def initialize_parameter_names return attribute_names.map(&:to_sym) if respond_to?(:attribute_names) return attribute_types.keys.map(&:to_sym) if Rails::VERSION::MAJOR <= 5 && respond_to?(:attribute_types) initialize_parameters.map(&:last) end def initialize_parameters @initialize_parameters ||= instance_method(:initialize).parameters end def provided_collection_parameter @provided_collection_parameter ||= nil end end ActiveSupport.run_load_hooks(:view_component, self) end end view_component-3.8.0/lib/view_component/engine.rb0000644000004100000410000001357514537061632022224 0ustar www-datawww-data# frozen_string_literal: true require "rails" require "view_component/config" require "view_component/deprecation" module ViewComponent class Engine < Rails::Engine # :nodoc: config.view_component = ViewComponent::Config.current rake_tasks do load "view_component/rails/tasks/view_component.rake" end initializer "view_component.set_configs" do |app| options = app.config.view_component %i[generate preview_controller preview_route show_previews_source].each do |config_option| options[config_option] ||= ViewComponent::Base.public_send(config_option) end options.instrumentation_enabled = false if options.instrumentation_enabled.nil? options.render_monkey_patch_enabled = true if options.render_monkey_patch_enabled.nil? options.show_previews = (Rails.env.development? || Rails.env.test?) if options.show_previews.nil? if options.show_previews # This is still necessary because when `config.view_component` is declared, `Rails.root` is unspecified. options.preview_paths << "#{Rails.root}/test/components/previews" if defined?(Rails.root) && Dir.exist?( "#{Rails.root}/test/components/previews" ) if options.show_previews_source require "method_source" app.config.to_prepare do MethodSource.instance_variable_set(:@lines_for_file, {}) end end end end initializer "view_component.enable_instrumentation" do |app| ActiveSupport.on_load(:view_component) do if app.config.view_component.instrumentation_enabled.present? # :nocov: Re-executing the below in tests duplicates initializers and causes order-dependent failures. ViewComponent::Base.prepend(ViewComponent::Instrumentation) if app.config.view_component.use_deprecated_instrumentation_name ViewComponent::Deprecation.deprecation_warning( "!render.view_component", "Use the new instrumentation key `render.view_component` instead. See https://viewcomponent.org/guide/instrumentation.html" ) end # :nocov: end end end # :nocov: initializer "view_component.enable_capture_patch" do |app| ActiveSupport.on_load(:view_component) do ActionView::Base.include(ViewComponent::CaptureCompatibility) if app.config.view_component.capture_compatibility_patch_enabled end end # :nocov: initializer "view_component.set_autoload_paths" do |app| options = app.config.view_component if options.show_previews && !options.preview_paths.empty? paths_to_add = options.preview_paths - ActiveSupport::Dependencies.autoload_paths ActiveSupport::Dependencies.autoload_paths.concat(paths_to_add) if paths_to_add.any? end end initializer "view_component.eager_load_actions" do ActiveSupport.on_load(:after_initialize) do ViewComponent::Base.descendants.each(&:compile) if Rails.application.config.eager_load end end initializer "view_component.monkey_patch_render" do |app| next if Rails.version.to_f >= 6.1 || !app.config.view_component.render_monkey_patch_enabled # :nocov: ActiveSupport.on_load(:action_view) do require "view_component/render_monkey_patch" ActionView::Base.prepend ViewComponent::RenderMonkeyPatch end ActiveSupport.on_load(:action_controller) do require "view_component/rendering_monkey_patch" require "view_component/render_to_string_monkey_patch" ActionController::Base.prepend ViewComponent::RenderingMonkeyPatch ActionController::Base.prepend ViewComponent::RenderToStringMonkeyPatch end # :nocov: end initializer "view_component.include_render_component" do |_app| next if Rails.version.to_f >= 6.1 # :nocov: ActiveSupport.on_load(:action_view) do require "view_component/render_component_helper" ActionView::Base.include ViewComponent::RenderComponentHelper end ActiveSupport.on_load(:action_controller) do require "view_component/rendering_component_helper" require "view_component/render_component_to_string_helper" ActionController::Base.include ViewComponent::RenderingComponentHelper ActionController::Base.include ViewComponent::RenderComponentToStringHelper end # :nocov: end initializer "static assets" do |app| if serve_static_preview_assets?(app.config) app.middleware.use(::ActionDispatch::Static, "#{root}/app/assets/vendor") end end def serve_static_preview_assets?(app_config) app_config.view_component.show_previews && app_config.public_file_server.enabled end initializer "compiler mode" do |_app| ViewComponent::Compiler.mode = if Rails.env.development? || Rails.env.test? ViewComponent::Compiler::DEVELOPMENT_MODE else ViewComponent::Compiler::PRODUCTION_MODE end end config.after_initialize do |app| options = app.config.view_component if options.show_previews app.routes.prepend do preview_controller = options.preview_controller.sub(/Controller$/, "").underscore get( options.preview_route, to: "#{preview_controller}#index", as: :preview_view_components, internal: true ) get( "#{options.preview_route}/*path", to: "#{preview_controller}#previews", as: :preview_view_component, internal: true ) end end if Rails.env.test? app.routes.prepend do get("_system_test_entrypoint", to: "view_components_system_test#system_test_entrypoint") end end app.executor.to_run :before do CompileCache.invalidate! unless ActionView::Base.cache_template_loading end end end end view_component-3.8.0/lib/view_component/rails/0000755000004100000410000000000014537061632021531 5ustar www-datawww-dataview_component-3.8.0/lib/view_component/rails/tasks/0000755000004100000410000000000014537061632022656 5ustar www-datawww-dataview_component-3.8.0/lib/view_component/rails/tasks/view_component.rake0000644000004100000410000000043314537061632026556 0ustar www-datawww-data# frozen_string_literal: true task stats: "view_component:statsetup" namespace :view_component do task :statsetup do # :nocov: require "rails/code_statistics" ::STATS_DIRECTORIES << ["ViewComponents", ViewComponent::Base.view_component_path] # :nocov: end end view_component-3.8.0/lib/view_component/system_test_helpers.rb0000644000004100000410000000165114537061632025054 0ustar www-datawww-data# frozen_string_literal: true module ViewComponent module SystemTestHelpers include TestHelpers # # Returns a block that can be used to visit the path of the inline rendered component. # @param fragment [Nokogiri::Fragment] The fragment returned from `render_inline`. # @param layout [String] The (optional) layout to use. # @return [Proc] A block that can be used to visit the path of the inline rendered component. def with_rendered_component_path(fragment, layout: false, &block) file = Tempfile.new( ["rendered_#{fragment.class.name}", ".html"], ViewComponentsSystemTestController.temp_dir ) begin file.write(vc_test_controller.render_to_string(html: fragment.to_html.html_safe, layout: layout)) file.rewind block.call("/_system_test_entrypoint?file=#{file.path.split("/").last}") ensure file.unlink end end end end view_component-3.8.0/lib/view_component/test_case.rb0000644000004100000410000000026414537061632022720 0ustar www-datawww-data# frozen_string_literal: true require "active_support/test_case" module ViewComponent class TestCase < ActiveSupport::TestCase include ViewComponent::TestHelpers end end view_component-3.8.0/lib/view_component/instrumentation.rb0000644000004100000410000000131214537061632024204 0ustar www-datawww-data# frozen_string_literal: true require "active_support/notifications" module ViewComponent # :nodoc: module Instrumentation def self.included(mod) mod.prepend(self) unless ancestors.include?(ViewComponent::Instrumentation) end def render_in(view_context, &block) ActiveSupport::Notifications.instrument( notification_name, { name: self.class.name, identifier: self.class.identifier } ) do super(view_context, &block) end end private def notification_name return "!render.view_component" if ViewComponent::Base.config.use_deprecated_instrumentation_name "render.view_component" end end end view_component-3.8.0/lib/view_component/render_component_helper.rb0000644000004100000410000000044714537061632025651 0ustar www-datawww-data# frozen_string_literal: true module ViewComponent module RenderComponentHelper # :nodoc: def render_component(component, &block) component.set_original_view_context(__vc_original_view_context) if is_a?(ViewComponent::Base) component.render_in(self, &block) end end end view_component-3.8.0/lib/view_component/docs_builder_component.html.erb0000644000004100000410000000104314537061632026572 0ustar www-datawww-data--- layout: default title: API reference nav_order: 3 --- # API <% @sections.each do |section| %> ## <%= section.heading %> <% section.methods.each do |method| %> ### <%== render ViewComponent::DocsBuilderComponent::MethodDoc.new(method, section.show_types) %> <% end %> <% section.error_klasses.each do |error_klass| %> ### <%== render ViewComponent::DocsBuilderComponent::ErrorKlassDoc.new(error_klass, section.show_types) %> <% end %> <% end %> view_component-3.8.0/lib/view_component/compiler.rb0000644000004100000410000002317314537061632022564 0ustar www-datawww-data# frozen_string_literal: true require "concurrent-ruby" module ViewComponent class Compiler # Compiler mode. Can be either: # * development (a blocking mode which ensures thread safety when redefining the `call` method for components, # default in Rails development and test mode) # * production (a non-blocking mode, default in Rails production mode) DEVELOPMENT_MODE = :development PRODUCTION_MODE = :production class_attribute :mode, default: PRODUCTION_MODE def initialize(component_class) @component_class = component_class @redefinition_lock = Mutex.new end def compiled? CompileCache.compiled?(component_class) end def development? self.class.mode == DEVELOPMENT_MODE end def compile(raise_errors: false, force: false) return if compiled? && !force return if component_class == ViewComponent::Base component_class.superclass.compile(raise_errors: raise_errors) if should_compile_superclass? if template_errors.present? raise TemplateError.new(template_errors) if raise_errors return false end if raise_errors component_class.validate_initialization_parameters! component_class.validate_collection_parameter! end if has_inline_template? template = component_class.inline_template redefinition_lock.synchronize do component_class.silence_redefinition_of_method("call") # rubocop:disable Style/EvalWithLocation component_class.class_eval <<-RUBY, template.path, template.lineno def call #{compiled_inline_template(template)} end RUBY # rubocop:enable Style/EvalWithLocation component_class.define_method("_call_#{safe_class_name}", component_class.instance_method(:call)) component_class.silence_redefinition_of_method("render_template_for") component_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1 def render_template_for(variant = nil) _call_#{safe_class_name} end RUBY end else templates.each do |template| method_name = call_method_name(template[:variant]) redefinition_lock.synchronize do component_class.silence_redefinition_of_method(method_name) # rubocop:disable Style/EvalWithLocation component_class.class_eval <<-RUBY, template[:path], 0 def #{method_name} #{compiled_template(template[:path])} end RUBY # rubocop:enable Style/EvalWithLocation end end define_render_template_for end component_class.build_i18n_backend CompileCache.register(component_class) end private attr_reader :component_class, :redefinition_lock def define_render_template_for variant_elsifs = variants.compact.uniq.map do |variant| safe_name = "_call_variant_#{normalized_variant_name(variant)}_#{safe_class_name}" component_class.define_method(safe_name, component_class.instance_method(call_method_name(variant))) "elsif variant.to_sym == :'#{variant}'\n #{safe_name}" end.join("\n") component_class.define_method("_call_#{safe_class_name}", component_class.instance_method(:call)) body = <<-RUBY if variant.nil? _call_#{safe_class_name} #{variant_elsifs} else _call_#{safe_class_name} end RUBY redefinition_lock.synchronize do component_class.silence_redefinition_of_method(:render_template_for) component_class.class_eval <<-RUBY, __FILE__, __LINE__ + 1 def render_template_for(variant = nil) #{body} end RUBY end end def has_inline_template? component_class.respond_to?(:inline_template) && component_class.inline_template.present? end def template_errors @__vc_template_errors ||= begin errors = [] if (templates + inline_calls).empty? && !has_inline_template? errors << "Couldn't find a template file or inline render method for #{component_class}." end if templates.count { |template| template[:variant].nil? } > 1 errors << "More than one template found for #{component_class}. " \ "There can only be one default template file per component." end invalid_variants = templates .group_by { |template| template[:variant] } .map { |variant, grouped| variant if grouped.length > 1 } .compact .sort unless invalid_variants.empty? errors << "More than one template found for #{"variant".pluralize(invalid_variants.count)} " \ "#{invalid_variants.map { |v| "'#{v}'" }.to_sentence} in #{component_class}. " \ "There can only be one template file per variant." end if templates.find { |template| template[:variant].nil? } && inline_calls_defined_on_self.include?(:call) errors << "Template file and inline render method found for #{component_class}. " \ "There can only be a template file or inline render method per component." end duplicate_template_file_and_inline_variant_calls = templates.pluck(:variant) & variants_from_inline_calls(inline_calls_defined_on_self) unless duplicate_template_file_and_inline_variant_calls.empty? count = duplicate_template_file_and_inline_variant_calls.count errors << "Template #{"file".pluralize(count)} and inline render #{"method".pluralize(count)} " \ "found for #{"variant".pluralize(count)} " \ "#{duplicate_template_file_and_inline_variant_calls.map { |v| "'#{v}'" }.to_sentence} " \ "in #{component_class}. " \ "There can only be a template file or inline render method per variant." end uniq_variants = variants.compact.uniq normalized_variants = uniq_variants.map { |variant| normalized_variant_name(variant) } colliding_variants = uniq_variants.select do |variant| normalized_variants.count(normalized_variant_name(variant)) > 1 end unless colliding_variants.empty? errors << "Colliding templates #{colliding_variants.sort.map { |v| "'#{v}'" }.to_sentence} " \ "found in #{component_class}." end errors end end def templates @templates ||= begin extensions = ActionView::Template.template_handler_extensions component_class.sidecar_files(extensions).each_with_object([]) do |path, memo| pieces = File.basename(path).split(".") memo << { path: path, variant: pieces[1..-2].join(".").split("+").second&.to_sym, handler: pieces.last } end end end def inline_calls @inline_calls ||= begin # Fetch only ViewComponent ancestor classes to limit the scope of # finding inline calls view_component_ancestors = ( component_class.ancestors.take_while { |ancestor| ancestor != ViewComponent::Base } - component_class.included_modules ) view_component_ancestors.flat_map { |ancestor| ancestor.instance_methods(false).grep(/^call(_|$)/) }.uniq end end def inline_calls_defined_on_self @inline_calls_defined_on_self ||= component_class.instance_methods(false).grep(/^call(_|$)/) end def variants @__vc_variants = ( templates.map { |template| template[:variant] } + variants_from_inline_calls(inline_calls) ).compact.uniq end def variants_from_inline_calls(calls) calls.reject { |call| call == :call }.map do |variant_call| variant_call.to_s.sub("call_", "").to_sym end end def compiled_inline_template(template) handler = ActionView::Template.handler_for_extension(template.language) template.rstrip! if component_class.strip_trailing_whitespace? compile_template(template.source, handler) end def compiled_template(file_path) handler = ActionView::Template.handler_for_extension(File.extname(file_path).delete(".")) template = File.read(file_path) compile_template(template, handler) end def compile_template(template, handler) template.rstrip! if component_class.strip_trailing_whitespace? if handler.method(:call).parameters.length > 1 handler.call(component_class, template) # :nocov: else handler.call( OpenStruct.new( source: template, identifier: component_class.identifier, type: component_class.type ) ) end # :nocov: end def call_method_name(variant) if variant.present? && variants.include?(variant) "call_#{normalized_variant_name(variant)}" else "call" end end def normalized_variant_name(variant) variant.to_s.gsub("-", "__").gsub(".", "___") end def safe_class_name @safe_class_name ||= component_class.name.underscore.gsub("/", "__") end def should_compile_superclass? development? && templates.empty? && !has_inline_template? && !call_defined? end def call_defined? component_class.instance_methods(false).include?(:call) || component_class.private_instance_methods(false).include?(:call) end end end view_component-3.8.0/lib/view_component/capture_compatibility.rb0000644000004100000410000000312614537061632025342 0ustar www-datawww-data# frozen_string_literal: true module ViewComponent # CaptureCompatibility is a module that patches #capture to fix issues # related to ViewComponent and functionality that relies on `capture` # like forms, capture itself, turbo frames, etc. # # This underlying incompatibility with ViewComponent and capture is # that several features like forms keep a reference to the primary # `ActionView::Base` instance which has its own @output_buffer. When # `#capture` is called on the original `ActionView::Base` instance while # evaluating a block from a ViewComponent the @output_buffer is overridden # in the ActionView::Base instance, and *not* the component. This results # in a double render due to `#capture` implementation details. # # To resolve the issue, we override `#capture` so that we can delegate # the `capture` logic to the ViewComponent that created the block. module CaptureCompatibility def self.included(base) return if base < InstanceMethods base.class_eval do alias_method :original_capture, :capture end base.prepend(InstanceMethods) end module InstanceMethods def capture(*args, &block) # Handle blocks that originate from C code and raise, such as `&:method` return original_capture(*args, &block) if block.source_location.nil? block_context = block.binding.receiver if block_context != self && block_context.class < ActionView::Base block_context.original_capture(*args, &block) else original_capture(*args, &block) end end end end end view_component-3.8.0/lib/view_component/component_error.rb0000644000004100000410000000014514537061632024157 0ustar www-datawww-data# frozen_string_literal: true module ViewComponent class ComponentError < StandardError end end view_component-3.8.0/lib/view_component/preview.rb0000644000004100000410000000652714537061632022437 0ustar www-datawww-data# frozen_string_literal: true require "active_support/descendants_tracker" module ViewComponent # :nodoc: class Preview include Rails.application.routes.url_helpers if defined?(Rails.application.routes.url_helpers) include ActionView::Helpers::TagHelper include ActionView::Helpers::AssetTagHelper extend ActiveSupport::DescendantsTracker def render(component, **args, &block) { args: args, block: block, component: component, locals: {}, template: "view_components/preview" } end def render_with_template(template: nil, locals: {}) { template: template, locals: locals } end alias_method :render_component, :render class << self # Returns all component preview classes. def all load_previews descendants end # Returns the arguments for rendering of the component in its layout def render_args(example, params: {}) example_params_names = instance_method(example).parameters.map(&:last) provided_params = params.slice(*example_params_names).to_h.symbolize_keys result = provided_params.empty? ? new.public_send(example) : new.public_send(example, **provided_params) result ||= {} result[:template] = preview_example_template_path(example) if result[:template].nil? @layout = nil unless defined?(@layout) result.merge(layout: @layout) end # Returns all of the available examples for the component preview. def examples public_instance_methods(false).map(&:to_s).sort end # Returns +true+ if the preview exists. def exists?(preview) all.any? { |p| p.preview_name == preview } end # Find a component preview by its underscored class name. def find(preview) all.find { |p| p.preview_name == preview } end # Returns the underscored name of the component preview without the suffix. def preview_name name.chomp("Preview").underscore end # rubocop:disable Style/TrivialAccessors # Setter for layout name. def layout(layout_name) @layout = layout_name end # rubocop:enable Style/TrivialAccessors # Returns the relative path (from preview_path) to the preview example template if the template exists def preview_example_template_path(example) preview_path = Array(preview_paths).detect do |path| Dir["#{path}/#{preview_name}_preview/#{example}.html.*"].first end raise MissingPreviewTemplateError.new(example) if preview_path.nil? path = Dir["#{preview_path}/#{preview_name}_preview/#{example}.html.*"].first Pathname.new(path) .relative_path_from(Pathname.new(preview_path)) .to_s .sub(/\..*$/, "") end # Returns the method body for the example from the preview file. def preview_source(example) source = instance_method(example.to_sym).source.split("\n") source[1...(source.size - 1)].join("\n") end def load_previews Array(preview_paths).each do |preview_path| Dir["#{preview_path}/**/*_preview.rb"].sort.each { |file| require_dependency file } end end private def preview_paths Base.preview_paths end end end end view_component-3.8.0/lib/view_component/rendering_component_helper.rb0000644000004100000410000000032314537061632026340 0ustar www-datawww-data# frozen_string_literal: true module ViewComponent module RenderingComponentHelper # :nodoc: def render_component(component) self.response_body = component.render_in(view_context) end end end view_component-3.8.0/lib/view_component/compile_cache.rb0000644000004100000410000000103014537061632023511 0ustar www-datawww-data# frozen_string_literal: true module ViewComponent # Keeps track of which templates have already been compiled # This isn't part of the public API module CompileCache mattr_accessor :cache, instance_reader: false, instance_accessor: false do Set.new end module_function def register(klass) cache << klass end def compiled?(klass) cache.include? klass end def invalidate_class!(klass) cache.delete(klass) end def invalidate! cache.clear end end end view_component-3.8.0/lib/view_component/config.rb0000644000004100000410000001500114537061632022206 0ustar www-datawww-data# frozen_string_literal: true require "view_component/deprecation" module ViewComponent class Config class << self # `new` without any arguments initializes the default configuration, but # it's important to differentiate in case that's no longer the case in # future. alias_method :default, :new def defaults ActiveSupport::OrderedOptions.new.merge!({ generate: default_generate_options, preview_controller: "ViewComponentsController", preview_route: "/rails/view_components", show_previews_source: false, instrumentation_enabled: false, use_deprecated_instrumentation_name: true, render_monkey_patch_enabled: true, view_component_path: "app/components", component_parent_class: nil, show_previews: Rails.env.development? || Rails.env.test?, preview_paths: default_preview_paths, test_controller: "ApplicationController", default_preview_layout: nil, capture_compatibility_patch_enabled: false }) end # @!attribute generate # @return [ActiveSupport::OrderedOptions] # The subset of configuration options relating to generators. # # All options under this namespace default to `false` unless otherwise # stated. # # #### `#sidecar` # # Always generate a component with a sidecar directory: # # config.view_component.generate.sidecar = true # # #### `#stimulus_controller` # # Always generate a Stimulus controller alongside the component: # # config.view_component.generate.stimulus_controller = true # # #### `#locale` # # Always generate translations file alongside the component: # # config.view_component.generate.locale = true # # #### `#distinct_locale_files` # # Always generate as many translations files as available locales: # # config.view_component.generate.distinct_locale_files = true # # One file will be generated for each configured `I18n.available_locales`, # falling back to `[:en]` when no `available_locales` is defined. # # #### `#preview` # # Always generate a preview alongside the component: # # config.view_component.generate.preview = true # # #### #preview_path # # Path to generate preview: # # config.view_component.generate.preview_path = "test/components/previews" # # Required when there is more than one path defined in preview_paths. # Defaults to `""`. If this is blank, the generator will use # `ViewComponent.config.preview_paths` if defined, # `"test/components/previews"` otherwise # @!attribute preview_controller # @return [String] # The controller used for previewing components. # Defaults to `ViewComponentsController`. # @!attribute preview_route # @return [String] # The entry route for component previews. # Defaults to `"/rails/view_components"`. # @!attribute show_previews_source # @return [Boolean] # Whether to display source code previews in component previews. # Defaults to `false`. # @!attribute instrumentation_enabled # @return [Boolean] # Whether ActiveSupport notifications are enabled. # Defaults to `false`. # @!attribute use_deprecated_instrumentation_name # @return [Boolean] # Whether ActiveSupport Notifications use the private name `"!render.view_component"` # or are made more publicly available via `"render.view_component"`. # Will default to `false` in next major version. # Defaults to `true`. # @!attribute render_monkey_patch_enabled # @return [Boolean] Whether the #render method should be monkey patched. # If this is disabled, use `#render_component` or # `#render_component_to_string` instead. # Defaults to `true`. # @!attribute view_component_path # @return [String] # The path in which components, their templates, and their sidecars should # be stored. # Defaults to `"app/components"`. # @!attribute component_parent_class # @return [String] # The parent class from which generated components will inherit. # Defaults to `nil`. If this is falsy, generators will use # `"ApplicationComponent"` if defined, `"ViewComponent::Base"` otherwise. # @!attribute show_previews # @return [Boolean] # Whether component previews are enabled. # Defaults to `true` in development and test environments. # @!attribute preview_paths # @return [Array] # The locations in which component previews will be looked up. # Defaults to `['test/component/previews']` relative to your Rails root. # @!attribute test_controller # @return [String] # The controller used for testing components. # Can also be configured on a per-test basis using `#with_controller_class`. # Defaults to `ApplicationController`. # @!attribute default_preview_layout # @return [String] # A custom default layout used for the previews index page and individual # previews. # Defaults to `nil`. If this is falsy, `"component_preview"` is used. # @!attribute capture_compatibility_patch_enabled # @return [Boolean] # Enables the experimental capture compatibility patch that makes ViewComponent # compatible with forms, capture, and other built-ins. # previews. # Defaults to `false`. def default_preview_paths return [] unless defined?(Rails.root) && Dir.exist?("#{Rails.root}/test/components/previews") ["#{Rails.root}/test/components/previews"] end def default_generate_options options = ActiveSupport::OrderedOptions.new(false) options.preview_path = "" options end end # @!attribute current # @return [ViewComponent::Config] # Returns the current ViewComponent::Config. This is persisted against this # class so that config options remain accessible before the rest of # ViewComponent has loaded. Defaults to an instance of ViewComponent::Config # with all other documented defaults set. class_attribute :current, default: defaults, instance_predicate: false def initialize @config = self.class.defaults end delegate_missing_to :config private attr_reader :config end end view_component-3.8.0/lib/view_component/collection.rb0000644000004100000410000000350314537061632023100 0ustar www-datawww-data# frozen_string_literal: true require "action_view/renderer/collection_renderer" if Rails.version.to_f >= 6.1 module ViewComponent class Collection include Enumerable attr_reader :component delegate :format, to: :component delegate :size, to: :@collection attr_accessor :__vc_original_view_context def set_original_view_context(view_context) self.__vc_original_view_context = view_context end def render_in(view_context, &block) components.map do |component| component.set_original_view_context(__vc_original_view_context) component.render_in(view_context, &block) end.join.html_safe end def components return @components if defined? @components iterator = ActionView::PartialIteration.new(@collection.size) component.validate_collection_parameter!(validate_default: true) @components = @collection.map do |item| component.new(**component_options(item, iterator)).tap do |component| iterator.iterate! end end end def each(&block) components.each(&block) end private def initialize(component, object, **options) @component = component @collection = collection_variable(object || []) @options = options end def collection_variable(object) if object.respond_to?(:to_ary) object.to_ary else raise InvalidCollectionArgumentError end end def component_options(item, iterator) item_options = {component.collection_parameter => item} item_options[component.collection_counter_parameter] = iterator.index if component.counter_argument_present? item_options[component.collection_iteration_parameter] = iterator.dup if component.iteration_argument_present? @options.merge(item_options) end end end view_component-3.8.0/LICENSE.txt0000644000004100000410000000210314537061632016434 0ustar www-datawww-dataMIT License Copyright (c) 2018-present ViewComponent contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.