view_component-props 0.0.0 → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 43f3079e4018e9a3be12b0fb7f5be91bbdc1c7cfaf374ad95b03f8a498752d9b
4
- data.tar.gz: 4e474c4a200df4766acae82ca46b0f165a8d1f3576f0e0acb9377369ccb076eb
3
+ metadata.gz: f80ece3769d7472183a42e86a7fc2a37264f5c59b8131d25f9294d6d64f49fae
4
+ data.tar.gz: e95f39dfd49633c8ed3a32ae61c09ceba1cd06c48dccda877a54017dc45c440d
5
5
  SHA512:
6
- metadata.gz: 38a4e165b9da70823b05c2189f947f30ffb3497a129f4d4d5db517db0c3956a741956b97b835ee9055d1005ac61697b0acaf0c9fee3b9e49b55e5f3befa077e1
7
- data.tar.gz: 67c7850c68584f7f2773cf3f885c1596a3f34131a72ebfefb5193609ef36d287ee996290ff861c764a64ae49adb62ae2fb051d9b1d7d06a08069b525f5ca1270
6
+ metadata.gz: 057e8e1859b94a01f253211516527f3593d840dfdbb50db30832c24b68965bcf966cf2a0fa35b27443fb54a40d25c1ff90f5a8dd7dc6b990d5fa184981ef90bf
7
+ data.tar.gz: f01e9685447e35ae07715326302abf184e626dc3e39e1ecc151a5ab4282d54eecc6c331cb7265bdd95eb45e9ab34573e1f863b3f6d151cb259a754da019071b7
data/CHANGELOG.md CHANGED
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [X.X.X] - YYYY-MM-DD
9
9
 
10
+ ## [0.0.1] - 2026-05-29
11
+
12
+ ### Added
13
+
14
+ - `prop` DSL with `default`, `fallback`, `required`, `cast`, `enum`, `validate`, and `description` options
15
+ - Resolved `props` (frozen indifferent hash) and `raw_props` on each instance; `prop_definitions` on the component class for introspection
16
+ - Built-in casters: `:integer`, `:float`, `:string`, `:symbol`, `:boolean`, `:array`, `:hash`, `:decimal`, `:date`, `:datetime`
17
+ - `ViewComponentProps.configure` for global settings and `register_caster` for custom or overriding casters
18
+ - Auto-install into `ViewComponent::Base` (Rails Railtie after initializers, or on require outside Rails); `#initialize` accepts a props hash and invokes `#after_initialize` after resolution
19
+ - Global `reject_undefined_props` configuration and per-class `reject_undefined_props!` / `permit_undefined_props!`
20
+ - `ViewComponentProps.install!` for patching additional base classes outside `ViewComponent::Base`
21
+ - Typed errors under `ViewComponentProps::Error` (`UnknownOptionError`, `UnknownCastError`, `CastError`, `RequiredPropError`, `InvalidEnumValueError`, `ValidationFailedError`, `UnknownPropsError`)
22
+
10
23
  ## [0.0.0] - 2026-05-20
11
24
 
12
25
  - Initial repository scaffolding and gem publication. No functionality yet.
data/README.md CHANGED
@@ -1,14 +1,17 @@
1
- # ViewComponent::Props
1
+ # ViewComponentProps
2
2
 
3
- A [ViewComponent](https://viewcomponent.org) extension for working with component props.
3
+ A [ViewComponent](https://viewcomponent.org) extension that adds a `prop` DSL to components: defaults, fallbacks, required props, casting, enum validation, custom validators, and a pluggable caster registry.
4
4
 
5
- > [!NOTE]
6
- > This gem is in early development. The public API is not yet stable and there is no usable functionality in this release. Documentation and examples will land alongside the first feature release.
5
+ In Rails apps, a Railtie patches `ViewComponent::Base` during boot (enabled by default). Outside Rails, the gem patches on require. Inherit as usual and pass a props hash to `new`.
7
6
 
8
7
  ## Requirements
9
8
 
10
9
  - Ruby >= 3.0
11
- - ViewComponent >= 3.0
10
+ - [ViewComponent](https://viewcomponent.org) >= 3.0, < 5.0
11
+ - ActiveSupport >= 6.0, < 9.0
12
+ - ActiveModel >= 6.0, < 9.0
13
+
14
+ Rails is supported via a Railtie but not required; the gem auto-installs into `ViewComponent::Base` on require when Rails is not loaded.
12
15
 
13
16
  ## Installation
14
17
 
@@ -26,7 +29,151 @@ bundle install
26
29
 
27
30
  ## Usage
28
31
 
29
- Coming soon.
32
+ ```ruby
33
+ class ButtonComponent < ViewComponent::Base
34
+ CLASS = %w[
35
+ px-9
36
+ py-3
37
+ text-lg
38
+ font-medium
39
+ tracking-wider
40
+ ring-0
41
+ ].freeze
42
+
43
+ prop :label, required: true
44
+ prop :shape, cast: :symbol, enum: %i[pill rounded rectangle], default: :pill
45
+ prop :disabled, cast: :boolean, default: false
46
+
47
+ def before_render
48
+ @class = cn(CLASS, {
49
+ "rounded-full" => @props[:shape] == :pill,
50
+ "rounded-xl" => @props[:shape] == :rounded,
51
+ "rounded-none" => @props[:shape] == :rectangle,
52
+ }, @props[:class])
53
+ end
54
+
55
+ def call
56
+ tag.button(@props[:label], class: @class, disabled: @props[:disabled])
57
+ end
58
+ end
59
+
60
+ render ButtonComponent.new(label: "Save", shape: :rounded)
61
+ ```
62
+
63
+ Resolved props are available as `props` (or `@props`), a frozen hash you can read with either string or symbol keys. The original input, before any casting or defaults are applied, is available as `raw_props` (or `@raw_props`).
64
+
65
+ Once props are resolved, `#after_initialize` runs, so you can override it for any setup that depends on `props`.
66
+
67
+ ### Options
68
+
69
+ Each `prop` accepts:
70
+
71
+ | Option | Purpose |
72
+ | -------------- | ---------------------------------------------------------------- |
73
+ | `default:` | Value (or callable) used when the key is missing from the input. |
74
+ | `fallback:` | Value (or callable) used when the resolved value is `nil`. |
75
+ | `required:` | Raises `RequiredPropError` when the resolved value is `nil`. |
76
+ | `cast:` | Coerces the value. A built-in caster name or any callable. |
77
+ | `enum:` | Restricts the value to a list of allowed values. |
78
+ | `validate:` | Callable that must return truthy for the value to be accepted. |
79
+ | `description:` | Free-form string for documentation and tooling. |
80
+
81
+ Defaults and fallbacks can also be callables, evaluated in the context of the component instance. Use `raw_props` to read other props from the constructor input:
82
+
83
+ ```ruby
84
+ class AvatarComponent < ViewComponent::Base
85
+ prop :user, required: true
86
+ prop :alt, default: -> { "#{raw_props[:user].name}'s avatar" }
87
+ end
88
+ ```
89
+
90
+ ### Casters
91
+
92
+ Built-in casters: `:integer`, `:float`, `:string`, `:symbol`, `:boolean`, `:array`, `:hash`, `:decimal`, `:date`, `:datetime`.
93
+
94
+ A `nil` value is never cast and stays `nil`. Use `default:` or `fallback:` when you need a value for a missing or `nil` prop.
95
+
96
+ Register custom casters in the configuration block:
97
+
98
+ ```ruby
99
+ ViewComponentProps.configure do |config|
100
+ config.register_caster(:slug) do |value|
101
+ value.to_s.parameterize
102
+ end
103
+ end
104
+
105
+ class HeadingComponent < ViewComponent::Base
106
+ prop :anchor, cast: :slug
107
+ end
108
+ ```
109
+
110
+ Built-in casters can be overridden the same way by registering the same key again in `configure`.
111
+
112
+ Or pass a lambda inline:
113
+
114
+ ```ruby
115
+ prop :code, cast: ->(value) { value.to_s.upcase }
116
+ ```
117
+
118
+ ### Strict props
119
+
120
+ ```ruby
121
+ class FormFieldComponent < ViewComponent::Base
122
+ reject_undefined_props!
123
+
124
+ prop :name, required: true
125
+ prop :label
126
+ end
127
+
128
+ FormFieldComponent.new(name: "email", typo: true)
129
+ # => raises ViewComponentProps::UnknownPropsError
130
+ ```
131
+
132
+ ### Custom base classes
133
+
134
+ `ViewComponentProps.install!(MyComponentBase)` applies the same patch to another class (idempotent). Useful if your app uses a shared component superclass that does **not** inherit from `ViewComponent::Base`.
135
+
136
+ `install!` no-ops when the target already includes `Definable` through any ancestor, so calling it on a subclass of `ViewComponent::Base` does nothing (the patch is already inherited). You only need it for base classes outside the `ViewComponent::Base` hierarchy.
137
+
138
+ ## Configuration
139
+
140
+ ### Rails
141
+
142
+ Disable auto-install in `config/application.rb`:
143
+
144
+ ```ruby
145
+ config.view_component_props.auto_include = false
146
+ ```
147
+
148
+ When enabled (the default), `ViewComponent::Base` is patched after initializers load so `config/initializers` can configure the gem first.
149
+
150
+ ### Initializer
151
+
152
+ ```ruby
153
+ ViewComponentProps.configure do |config|
154
+ config.reject_undefined_props = true
155
+
156
+ config.register_caster(:slug) do |value|
157
+ value.to_s.parameterize
158
+ end
159
+ end
160
+ ```
161
+
162
+ When `reject_undefined_props` is `true`, every component rejects unknown prop keys by default. Override per class with `reject_undefined_props!` or `permit_undefined_props!`.
163
+
164
+ ### Non-Rails
165
+
166
+ Auto-install runs on require when `configuration.auto_include` is `true` (the default). Because it is read at require time, you must configure it **before** requiring the gem for it to have any effect:
167
+
168
+ ```ruby
169
+ require "view_component_props/configuration"
170
+ ViewComponentProps.configure { |config| config.auto_include = false }
171
+
172
+ require "view_component_props"
173
+ ViewComponentProps.install!
174
+ ```
175
+
176
+ Configuring `auto_include` _after_ `require "view_component_props"` cannot reverse the install that already happened. Note this `configuration.auto_include` switch is consulted only outside Rails; in Rails the equivalent switch is `config.view_component_props.auto_include`, handled by the Railtie.
30
177
 
31
178
  ## Development
32
179
 
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponentProps
4
+ module Casters
5
+ class Base
6
+ BOOLEAN_TYPE = ActiveModel::Type::Boolean.new
7
+
8
+ class_attribute :base_casters, instance_accessor: false, default: HashWithIndifferentAccess.new
9
+
10
+ def self.register_caster(key, &block)
11
+ Casters.registry[key] = block
12
+ self.base_casters = base_casters.merge(key => block)
13
+ end
14
+
15
+ register_caster(:integer) do |value|
16
+ Integer(value)
17
+ end
18
+
19
+ register_caster(:float) do |value|
20
+ Float(value)
21
+ end
22
+
23
+ register_caster(:string) do |value|
24
+ value.to_s
25
+ end
26
+
27
+ register_caster(:symbol) do |value|
28
+ value.to_sym
29
+ end
30
+
31
+ register_caster(:boolean) do |value|
32
+ BOOLEAN_TYPE.cast(value)
33
+ end
34
+
35
+ register_caster(:array) do |value|
36
+ Array(value)
37
+ end
38
+
39
+ register_caster(:hash) do |value|
40
+ value.is_a?(Hash) ? value : value.to_h
41
+ end
42
+
43
+ register_caster(:decimal) do |value|
44
+ value.is_a?(BigDecimal) ? value : BigDecimal(value.to_s)
45
+ end
46
+
47
+ register_caster(:date) do |value|
48
+ value.is_a?(Date) ? value : Date.parse(value.to_s)
49
+ end
50
+
51
+ register_caster(:datetime) do |value|
52
+ if value.is_a?(Time) || value.is_a?(DateTime)
53
+ value
54
+ else
55
+ Time.zone ? Time.zone.parse(value.to_s) : Time.parse(value.to_s)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/indifferent_access"
4
+ require "active_support/core_ext/module/attribute_accessors"
5
+ require "bigdecimal"
6
+ require "date"
7
+ require "time"
8
+ require "active_model"
9
+ require "active_model/type"
10
+ require "active_model/type/boolean"
11
+
12
+ require_relative "configuration"
13
+
14
+ module ViewComponentProps
15
+ module Casters
16
+ mattr_accessor :registry, default: HashWithIndifferentAccess.new
17
+
18
+ class << self
19
+ def fetch(key)
20
+ registry.fetch(key)
21
+ end
22
+
23
+ def known?(key)
24
+ registry.key?(key)
25
+ end
26
+
27
+ def reset!
28
+ self.registry = Base.base_casters.merge(ViewComponentProps.configuration.custom_casters)
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ require_relative "casters/base"
35
+
36
+ ViewComponentProps::Casters.reset!
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/indifferent_access"
4
+
5
+ module ViewComponentProps
6
+ class << self
7
+ def configuration
8
+ @configuration ||= Configuration.new
9
+ end
10
+
11
+ def configure
12
+ yield(configuration)
13
+ end
14
+
15
+ def reset_configuration!
16
+ @configuration = Configuration.new
17
+ Casters.reset! if defined?(Casters)
18
+ end
19
+ end
20
+
21
+ class Configuration
22
+ attr_accessor :auto_include
23
+ attr_accessor :reject_undefined_props
24
+ attr_reader :custom_casters
25
+
26
+ def initialize
27
+ @auto_include = true
28
+ @reject_undefined_props = false
29
+ @custom_casters = HashWithIndifferentAccess.new
30
+ end
31
+
32
+ def register_caster(key, &block)
33
+ raise ArgumentError, "register_caster requires a block" unless block
34
+
35
+ custom_casters[key] = block
36
+ Casters.registry[key] = block if defined?(Casters)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module ViewComponentProps
6
+ module Definable
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ class_attribute :prop_definitions, instance_accessor: false, default: HashWithIndifferentAccess.new
11
+ class_attribute :undefined_props_rejected, instance_accessor: false, default: nil
12
+
13
+ attr_reader :props
14
+ attr_reader :raw_props
15
+ end
16
+
17
+ class_methods do
18
+ def prop(key, options = {})
19
+ definition = Definition.new(key, options, component: name)
20
+ self.prop_definitions = prop_definitions.merge(key => definition)
21
+ end
22
+
23
+ def reject_undefined_props!
24
+ self.undefined_props_rejected = true
25
+ end
26
+
27
+ def permit_undefined_props!
28
+ self.undefined_props_rejected = false
29
+ end
30
+ end
31
+
32
+ def setup_props_for(props)
33
+ enforce_defined_props!(props) if undefined_props_rejected?
34
+
35
+ indifferent_props = props.with_indifferent_access
36
+ @raw_props = props.dup.freeze
37
+ @props = resolve_props(indifferent_props).freeze
38
+ end
39
+
40
+ def after_initialize; end
41
+
42
+ private
43
+
44
+ def undefined_props_rejected?
45
+ per_class_setting = self.class.undefined_props_rejected
46
+ return per_class_setting unless per_class_setting.nil?
47
+
48
+ ViewComponentProps.configuration.reject_undefined_props
49
+ end
50
+
51
+ def enforce_defined_props!(props)
52
+ declared_props = self.class.prop_definitions.keys.map(&:to_s)
53
+ unknown_props = props.keys.map(&:to_s) - declared_props
54
+ return if unknown_props.empty?
55
+
56
+ raise UnknownPropsError, "Unknown props for #{self.class.name}: #{unknown_props.map(&:to_sym).inspect}"
57
+ end
58
+
59
+ def resolve_props(props)
60
+ self.class.prop_definitions.each_with_object(props.dup) do |(key, definition), resolved|
61
+ resolved[key] = definition.call(props, self)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponentProps
4
+ class Definition
5
+ OPTION_KEYS = %i[
6
+ default
7
+ fallback
8
+ required
9
+ cast
10
+ enum
11
+ validate
12
+ description
13
+ ].freeze
14
+
15
+ attr_reader :key, :description
16
+
17
+ def initialize(key, options = {}, component:)
18
+ @key = key
19
+ @options = options
20
+ @component = component
21
+
22
+ validate_options!
23
+
24
+ cast = normalize_cast(@options[:cast])
25
+ @caster = build_caster(cast)
26
+ @cast_label = describe_cast(cast)
27
+ @required = @options[:required] || false
28
+ @enum = @options[:enum]
29
+ @validate = @options[:validate]
30
+ @default = @options[:default]
31
+ @fallback = @options[:fallback]
32
+ @description = @options[:description]
33
+ end
34
+
35
+ def call(props, instance = nil)
36
+ value = evaluate(props, instance)
37
+ casted_value = cast_value(value)
38
+
39
+ raise RequiredPropError, "Required prop :#{@key} for #{@component} cannot be nil" if casted_value.nil? && @required
40
+ return casted_value if casted_value.nil?
41
+
42
+ raise InvalidEnumValueError, "Prop :#{@key} for #{@component} must be one of #{@enum.inspect}, got #{casted_value.inspect}" if @enum && !@enum.include?(casted_value)
43
+ raise ValidationFailedError, "Prop :#{@key} for #{@component} failed validation, got #{casted_value.inspect}" if @validate && !@validate.call(casted_value)
44
+
45
+ casted_value
46
+ end
47
+
48
+ private
49
+
50
+ def validate_options!
51
+ unknown_keys = @options.keys - OPTION_KEYS
52
+ raise UnknownOptionError, "Unknown options for prop :#{@key}: #{unknown_keys.inspect}" if unknown_keys.any?
53
+
54
+ return unless @options.key?(:cast)
55
+
56
+ cast_option = @options[:cast]
57
+ raise UnknownCastError, "Cast type for prop :#{@key} cannot be nil" if cast_option.nil?
58
+ return if cast_option.respond_to?(:call)
59
+ return if cast_option.is_a?(Symbol) || cast_option.is_a?(String)
60
+
61
+ raise UnknownCastError, "Cast type for prop :#{@key} must be a Symbol, String, or callable, got #{cast_option.inspect}"
62
+ end
63
+
64
+ def normalize_cast(value)
65
+ return nil if value.nil?
66
+ return value if value.respond_to?(:call)
67
+
68
+ value.to_sym
69
+ end
70
+
71
+ def evaluate(props, instance)
72
+ props = props.with_indifferent_access unless props.is_a?(HashWithIndifferentAccess)
73
+ value = begin
74
+ if props.key?(@key)
75
+ props[@key]
76
+ elsif @options.key?(:default)
77
+ resolve(@default, instance)
78
+ end
79
+ end
80
+
81
+ value = resolve(@fallback, instance) if value.nil? && @options.key?(:fallback)
82
+ value
83
+ end
84
+
85
+ def build_caster(cast)
86
+ return nil if cast.nil?
87
+ return cast if cast.respond_to?(:call)
88
+
89
+ ->(value) { Casters.fetch(cast).call(value) }
90
+ end
91
+
92
+ def cast_value(value)
93
+ return value if value.nil? || @caster.nil?
94
+
95
+ @caster.call(value)
96
+ rescue ViewComponentProps::Error
97
+ raise
98
+ rescue ArgumentError, TypeError, NoMethodError, KeyError, RangeError => e
99
+ raise CastError, "Prop :#{@key} for #{@component} could not be cast to #{@cast_label} (got #{value.inspect}): #{e.message}"
100
+ end
101
+
102
+ def describe_cast(cast)
103
+ return nil if cast.nil?
104
+ return cast.inspect unless cast.respond_to?(:call)
105
+
106
+ location = cast.respond_to?(:source_location) ? cast.source_location : nil
107
+ location ? "callable at #{location.join(':')}" : "callable"
108
+ end
109
+
110
+ def resolve(value, instance)
111
+ return value unless value.respond_to?(:call)
112
+
113
+ instance ? instance.instance_exec(&value) : value.call
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponentProps
4
+ class Error < StandardError; end
5
+
6
+ class UnknownOptionError < Error; end
7
+ class UnknownCastError < Error; end
8
+ class CastError < Error; end
9
+ class RequiredPropError < Error; end
10
+ class InvalidEnumValueError < Error; end
11
+ class ValidationFailedError < Error; end
12
+ class UnknownPropsError < Error; end
13
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponentProps
4
+ class Railtie < Rails::Railtie
5
+ config.view_component_props = ActiveSupport::OrderedOptions.new
6
+ config.view_component_props.auto_include = true
7
+
8
+ initializer "view_component_props.install", after: :load_config_initializers do |application|
9
+ ViewComponentProps.install! if application.config.view_component_props.auto_include
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ViewComponentProps
4
+ VERSION = "0.0.1"
5
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/class/attribute"
4
+ require "active_support/core_ext/hash/indifferent_access"
5
+ require "view_component"
6
+
7
+ require_relative "view_component_props/configuration"
8
+ require_relative "view_component_props/errors"
9
+ require_relative "view_component_props/casters"
10
+ require_relative "view_component_props/definition"
11
+ require_relative "view_component_props/definable"
12
+ require_relative "view_component_props/version"
13
+
14
+ module ViewComponentProps
15
+ class << self
16
+ def install!(target = ViewComponent::Base)
17
+ return if target.include?(Definable)
18
+
19
+ target.include(Definable)
20
+ target.class_eval do
21
+ def initialize(props = {})
22
+ super()
23
+ setup_props_for(props)
24
+ after_initialize
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ if defined?(Rails::Railtie)
32
+ require_relative "view_component_props/railtie"
33
+ elsif ViewComponentProps.configuration.auto_include
34
+ ViewComponentProps.install!
35
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: view_component-props
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kinnell Shah
@@ -9,6 +9,26 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activemodel
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '6.0'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '9.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '6.0'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '9.0'
12
32
  - !ruby/object:Gem::Dependency
13
33
  name: activesupport
14
34
  requirement: !ruby/object:Gem::Requirement
@@ -49,8 +69,10 @@ dependencies:
49
69
  - - "<"
50
70
  - !ruby/object:Gem::Version
51
71
  version: '5.0'
52
- description: 'A ViewComponent extension for working with component props. Functionality
53
- is in active development.
72
+ description: 'A ViewComponent extension that adds a `prop` DSL with defaults, fallbacks,
73
+ required props, casting, enum validation, custom validators, and a pluggable caster
74
+ registry. Patches ViewComponent::Base via a Rails Railtie (or on require outside
75
+ Rails) so components accept a props hash out of the box.
54
76
 
55
77
  '
56
78
  email:
@@ -62,10 +84,15 @@ files:
62
84
  - CHANGELOG.md
63
85
  - LICENSE
64
86
  - README.md
65
- - lib/view_component/props.rb
66
- - lib/view_component/props/configuration.rb
67
- - lib/view_component/props/errors.rb
68
- - lib/view_component/props/version.rb
87
+ - lib/view_component_props.rb
88
+ - lib/view_component_props/casters.rb
89
+ - lib/view_component_props/casters/base.rb
90
+ - lib/view_component_props/configuration.rb
91
+ - lib/view_component_props/definable.rb
92
+ - lib/view_component_props/definition.rb
93
+ - lib/view_component_props/errors.rb
94
+ - lib/view_component_props/railtie.rb
95
+ - lib/view_component_props/version.rb
69
96
  homepage: https://github.com/kinnell/view_component-props
70
97
  licenses:
71
98
  - MIT
@@ -1,8 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ViewComponent
4
- module Props
5
- class Configuration # rubocop:disable Lint/EmptyClass
6
- end
7
- end
8
- end
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ViewComponent
4
- module Props
5
- class Error < StandardError; end
6
- end
7
- end
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ViewComponent
4
- module Props
5
- VERSION = "0.0.0"
6
- end
7
- end
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "active_support/concern"
4
- require "active_support/dependencies/autoload"
5
- require "active_support/core_ext/hash/indifferent_access"
6
- require "view_component"
7
-
8
- require_relative "props/configuration"
9
- require_relative "props/errors"
10
- require_relative "props/version"
11
-
12
- module ViewComponent
13
- module Props
14
- extend ActiveSupport::Concern
15
- extend ActiveSupport::Autoload
16
-
17
- class << self
18
- def configuration
19
- @configuration ||= Configuration.new
20
- end
21
-
22
- def configure
23
- yield(configuration)
24
- end
25
-
26
- def reset_configuration!
27
- @configuration = Configuration.new
28
- end
29
- end
30
- end
31
- end