phlexi-form 0.3.0 → 0.4.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/gemfiles/default.gemfile.lock +34 -32
  4. data/gemfiles/rails_7.gemfile.lock +16 -18
  5. data/lib/phlexi/form/base.rb +18 -9
  6. data/lib/phlexi/form/builder.rb +297 -0
  7. data/lib/phlexi/form/components/base.rb +1 -1
  8. data/lib/phlexi/form/components/input.rb +16 -2
  9. data/lib/phlexi/form/components/select.rb +4 -0
  10. data/lib/phlexi/form/html.rb +18 -0
  11. data/lib/phlexi/form/{field_options → options}/autofocus.rb +1 -1
  12. data/lib/phlexi/form/{field_options → options}/collection.rb +6 -2
  13. data/lib/phlexi/form/{field_options → options}/disabled.rb +1 -1
  14. data/lib/phlexi/form/{field_options → options}/errors.rb +11 -11
  15. data/lib/phlexi/form/options/hints.rb +13 -0
  16. data/lib/phlexi/form/options/inferred_types.rb +32 -0
  17. data/lib/phlexi/form/{field_options → options}/length.rb +3 -3
  18. data/lib/phlexi/form/{field_options → options}/limit.rb +2 -2
  19. data/lib/phlexi/form/options/max.rb +55 -0
  20. data/lib/phlexi/form/options/min.rb +55 -0
  21. data/lib/phlexi/form/{field_options → options}/pattern.rb +2 -2
  22. data/lib/phlexi/form/{field_options → options}/readonly.rb +1 -1
  23. data/lib/phlexi/form/{field_options → options}/required.rb +2 -2
  24. data/lib/phlexi/form/options/step.rb +39 -0
  25. data/lib/phlexi/form/options/validators.rb +24 -0
  26. data/lib/phlexi/form/structure/field_collection.rb +9 -29
  27. data/lib/phlexi/form/structure/namespace.rb +2 -114
  28. data/lib/phlexi/form/structure/namespace_collection.rb +1 -32
  29. data/lib/phlexi/form/theme.rb +160 -0
  30. data/lib/phlexi/form/version.rb +1 -1
  31. data/lib/phlexi/form.rb +3 -6
  32. metadata +34 -23
  33. data/lib/phlexi/form/field_options/associations.rb +0 -21
  34. data/lib/phlexi/form/field_options/hints.rb +0 -26
  35. data/lib/phlexi/form/field_options/inferred_types.rb +0 -159
  36. data/lib/phlexi/form/field_options/labels.rb +0 -28
  37. data/lib/phlexi/form/field_options/min_max.rb +0 -92
  38. data/lib/phlexi/form/field_options/multiple.rb +0 -65
  39. data/lib/phlexi/form/field_options/placeholder.rb +0 -18
  40. data/lib/phlexi/form/field_options/themes.rb +0 -207
  41. data/lib/phlexi/form/field_options/validators.rb +0 -48
  42. data/lib/phlexi/form/structure/dom.rb +0 -62
  43. data/lib/phlexi/form/structure/field_builder.rb +0 -243
  44. data/lib/phlexi/form/structure/node.rb +0 -28
@@ -3,19 +3,7 @@
3
3
  module Phlexi
4
4
  module Form
5
5
  module Structure
6
- class NamespaceCollection < Node
7
- include Enumerable
8
-
9
- def initialize(key, parent:, collection: nil, &block)
10
- raise ArgumentError, "block is required" unless block.present?
11
-
12
- super(key, parent: parent)
13
-
14
- @collection = collection
15
- @block = block
16
- each(&block)
17
- end
18
-
6
+ class NamespaceCollection < Phlexi::Field::Structure::NamespaceCollection
19
7
  def extract_input(params)
20
8
  namespace = build_namespace(0)
21
9
  @block.call(namespace)
@@ -23,25 +11,6 @@ module Phlexi
23
11
  inputs = params[key].map { |param| namespace.extract_input([param]) }
24
12
  {key => inputs}
25
13
  end
26
-
27
- private
28
-
29
- def each(&)
30
- namespaces.each(&)
31
- end
32
-
33
- # Builds and memoizes namespaces for the collection.
34
- #
35
- # @return [Array<Namespace>] An array of namespace objects.
36
- def namespaces
37
- @namespaces ||= @collection.map.with_index do |object, key|
38
- build_namespace(key, object: object)
39
- end
40
- end
41
-
42
- def build_namespace(index, **)
43
- parent.class.new(index, parent: self, builder_klass: parent.builder_klass, **)
44
- end
45
14
  end
46
15
  end
47
16
  end
@@ -0,0 +1,160 @@
1
+ module Phlexi
2
+ module Form
3
+ class Theme < Phlexi::Field::Theme
4
+ def self.theme
5
+ @theme ||= {
6
+ # == Base
7
+ base: nil,
8
+ hint: nil,
9
+ error: nil,
10
+ full_error: :error,
11
+ wrapper: nil,
12
+ inner_wrapper: nil,
13
+ button: nil,
14
+ submit_button: :button,
15
+
16
+ # == Label
17
+ label: nil,
18
+ invalid_label: nil,
19
+ valid_label: nil,
20
+ neutral_label: nil,
21
+
22
+ # == Input
23
+ input: nil,
24
+ valid_input: nil,
25
+ invalid_input: nil,
26
+ neutral_input: nil,
27
+
28
+ # String
29
+ string: :input,
30
+ valid_string: :valid_input,
31
+ invalid_string: :invalid_input,
32
+ neutral_string: :neutral_input,
33
+
34
+ # Email
35
+ email: :input,
36
+ valid_email: :valid_input,
37
+ invalid_email: :invalid_input,
38
+ neutral_email: :neutral_input,
39
+
40
+ # Password
41
+ password: :input,
42
+ valid_password: :valid_input,
43
+ invalid_password: :invalid_input,
44
+ neutral_password: :neutral_input,
45
+
46
+ # Phone
47
+ phone: :input,
48
+ valid_phone: :valid_input,
49
+ invalid_phone: :invalid_input,
50
+ neutral_phone: :neutral_input,
51
+
52
+ # Color
53
+ color: :input,
54
+ valid_color: :valid_input,
55
+ invalid_color: :invalid_input,
56
+ neutral_color: :neutral_input,
57
+
58
+ # Url
59
+ url: :input,
60
+ valid_url: :valid_input,
61
+ invalid_url: :invalid_input,
62
+ neutral_url: :neutral_input,
63
+
64
+ # Search
65
+ search: :input,
66
+ valid_search: :valid_input,
67
+ invalid_search: :invalid_input,
68
+ neutral_search: :neutral_input,
69
+
70
+ # Number
71
+ number: :input,
72
+ valid_number: :valid_input,
73
+ invalid_number: :invalid_input,
74
+ neutral_number: :neutral_input,
75
+
76
+ # Date
77
+ date: :input,
78
+ valid_date: :valid_input,
79
+ invalid_date: :invalid_input,
80
+ neutral_date: :neutral_input,
81
+
82
+ # Time
83
+ time: :input,
84
+ valid_time: :valid_input,
85
+ invalid_time: :invalid_input,
86
+ neutral_time: :neutral_input,
87
+
88
+ # DateTime
89
+ datetime: :input,
90
+ valid_datetime: :valid_input,
91
+ invalid_datetime: :invalid_input,
92
+ neutral_datetime: :neutral_input,
93
+
94
+ # Checkbox
95
+ checkbox: :input,
96
+ valid_checkbox: :valid_input,
97
+ invalid_checkbox: :invalid_input,
98
+ neutral_checkbox: :neutral_input,
99
+
100
+ # Boolean
101
+ boolan: :checkbox,
102
+ valid_boolan: :valid_checkbox,
103
+ invalid_boolan: :invalid_checkbox,
104
+ neutral_boolan: :neutral_checkbox,
105
+
106
+ # Textarea
107
+ textarea: :input,
108
+ valid_textarea: :valid_input,
109
+ invalid_textarea: :invalid_input,
110
+ neutral_textarea: :neutral_input,
111
+
112
+ # Hstore
113
+ hstore: :textarea,
114
+ valid_hstore: :valid_textarea,
115
+ invalid_hstore: :invalid_textarea,
116
+ neutral_hstore: :neutral_textarea,
117
+
118
+ # Select
119
+ select: :input,
120
+ valid_select: :valid_input,
121
+ invalid_select: :invalid_input,
122
+ neutral_select: :neutral_input,
123
+
124
+ # File
125
+ file: :input,
126
+ valid_file: :valid_input,
127
+ invalid_file: :invalid_input,
128
+ neutral_file: :neutral_input
129
+
130
+ }.freeze
131
+ end
132
+
133
+ # Resolves the theme for a component based on its current validity state
134
+ #
135
+ # This method determines the validity state of the field (valid, invalid, or neutral)
136
+ # and returns the corresponding theme by prepending the state to the component name.
137
+ #
138
+ # @param property [Symbol, String] The base theme property to resolve
139
+ # @return [String, nil] The resolved validity-specific theme or nil if not found
140
+ #
141
+ # @example Resolving a validity theme
142
+ # # Assuming the field has errors and the theme includes { invalid_input: "error-class" }
143
+ # resolve_validity_theme(:input)
144
+ # # => "error-class"
145
+ def resolve_validity_theme(property, field)
146
+ return unless field
147
+
148
+ validity_property = if field.has_errors?
149
+ :"invalid_#{property}"
150
+ elsif field.object_valid?
151
+ :"valid_#{property}"
152
+ else
153
+ :"neutral_#{property}"
154
+ end
155
+
156
+ resolve_theme(validity_property)
157
+ end
158
+ end
159
+ end
160
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Phlexi
4
4
  module Form
5
- VERSION = "0.3.0"
5
+ VERSION = "0.4.1"
6
6
  end
7
7
  end
data/lib/phlexi/form.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "zeitwerk"
4
4
  require "phlex"
5
+ require "phlexi-field"
5
6
  require "active_support/core_ext/object/blank"
6
7
 
7
8
  module Phlexi
@@ -11,21 +12,17 @@ module Phlexi
11
12
  loader.inflector.inflect(
12
13
  "phlexi-form" => "Phlexi",
13
14
  "phlexi" => "Phlexi",
14
- "dom" => "DOM"
15
+ "html" => "HTML"
15
16
  )
16
17
  loader.push_dir(File.expand_path("..", __dir__))
17
18
  loader.ignore(File.expand_path("../generators", __dir__))
18
19
  loader.setup
19
20
  end
20
21
 
21
- COMPONENT_BASE = (defined?(::ApplicationComponent) ? ::ApplicationComponent : Phlex::HTML)
22
-
23
- NIL_VALUE = :__i_phlexi_form_nil_value_i__
24
-
25
22
  class Error < StandardError; end
26
23
  end
27
24
  end
28
25
 
29
26
  def Phlexi.Form(...)
30
- Phlexi::Form::Base.new(...)
27
+ Phlexi::Form::Base.inline(...)
31
28
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phlexi-form
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Froelich
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-09-07 00:00:00.000000000 Z
11
+ date: 2024-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: phlex
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: phlexi-field
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: activesupport
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -190,6 +204,7 @@ files:
190
204
  - lib/phlexi-form.rb
191
205
  - lib/phlexi/form.rb
192
206
  - lib/phlexi/form/base.rb
207
+ - lib/phlexi/form/builder.rb
193
208
  - lib/phlexi/form/components/base.rb
194
209
  - lib/phlexi/form/components/checkbox.rb
195
210
  - lib/phlexi/form/components/collection_checkboxes.rb
@@ -211,31 +226,27 @@ files:
211
226
  - lib/phlexi/form/components/submit_button.rb
212
227
  - lib/phlexi/form/components/textarea.rb
213
228
  - lib/phlexi/form/components/wrapper.rb
214
- - lib/phlexi/form/field_options/associations.rb
215
- - lib/phlexi/form/field_options/autofocus.rb
216
- - lib/phlexi/form/field_options/collection.rb
217
- - lib/phlexi/form/field_options/disabled.rb
218
- - lib/phlexi/form/field_options/errors.rb
219
- - lib/phlexi/form/field_options/hints.rb
220
- - lib/phlexi/form/field_options/inferred_types.rb
221
- - lib/phlexi/form/field_options/labels.rb
222
- - lib/phlexi/form/field_options/length.rb
223
- - lib/phlexi/form/field_options/limit.rb
224
- - lib/phlexi/form/field_options/min_max.rb
225
- - lib/phlexi/form/field_options/multiple.rb
226
- - lib/phlexi/form/field_options/pattern.rb
227
- - lib/phlexi/form/field_options/placeholder.rb
228
- - lib/phlexi/form/field_options/readonly.rb
229
- - lib/phlexi/form/field_options/required.rb
230
- - lib/phlexi/form/field_options/themes.rb
231
- - lib/phlexi/form/field_options/validators.rb
229
+ - lib/phlexi/form/html.rb
232
230
  - lib/phlexi/form/option_mapper.rb
233
- - lib/phlexi/form/structure/dom.rb
234
- - lib/phlexi/form/structure/field_builder.rb
231
+ - lib/phlexi/form/options/autofocus.rb
232
+ - lib/phlexi/form/options/collection.rb
233
+ - lib/phlexi/form/options/disabled.rb
234
+ - lib/phlexi/form/options/errors.rb
235
+ - lib/phlexi/form/options/hints.rb
236
+ - lib/phlexi/form/options/inferred_types.rb
237
+ - lib/phlexi/form/options/length.rb
238
+ - lib/phlexi/form/options/limit.rb
239
+ - lib/phlexi/form/options/max.rb
240
+ - lib/phlexi/form/options/min.rb
241
+ - lib/phlexi/form/options/pattern.rb
242
+ - lib/phlexi/form/options/readonly.rb
243
+ - lib/phlexi/form/options/required.rb
244
+ - lib/phlexi/form/options/step.rb
245
+ - lib/phlexi/form/options/validators.rb
235
246
  - lib/phlexi/form/structure/field_collection.rb
236
247
  - lib/phlexi/form/structure/namespace.rb
237
248
  - lib/phlexi/form/structure/namespace_collection.rb
238
- - lib/phlexi/form/structure/node.rb
249
+ - lib/phlexi/form/theme.rb
239
250
  - lib/phlexi/form/version.rb
240
251
  - sig/phlexi/form.rbs
241
252
  homepage: https://github.com/radioactive-labs/phlexi-form
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Form
5
- module FieldOptions
6
- module Associations
7
- protected
8
-
9
- def association_reflection
10
- @association_reflection ||= find_association_reflection
11
- end
12
-
13
- def find_association_reflection
14
- if object.class.respond_to?(:reflect_on_association)
15
- object.class.reflect_on_association(key)
16
- end
17
- end
18
- end
19
- end
20
- end
21
- end
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Form
5
- module FieldOptions
6
- module Hints
7
- def hint(hint = nil)
8
- if hint.nil?
9
- options[:hint]
10
- else
11
- options[:hint] = hint
12
- self
13
- end
14
- end
15
-
16
- def has_hint?
17
- hint.present?
18
- end
19
-
20
- def show_hint?
21
- has_hint? && !show_errors?
22
- end
23
- end
24
- end
25
- end
26
- end
@@ -1,159 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bigdecimal"
4
-
5
- module Phlexi
6
- module Form
7
- module FieldOptions
8
- module InferredTypes
9
- def inferred_db_type
10
- @inferred_db_type ||= infer_db_type
11
- end
12
-
13
- # This will give you the component type e.g. input, textarea, select
14
- def inferred_component_type
15
- @inferred_component_type ||= infer_component_type
16
- end
17
-
18
- # This will give you the subtype of the input component e.g input type="text|date|checkbox" etc
19
- def inferred_input_component_subtype
20
- @inferred_input_component_subtype ||= infer_input_component_subtype(inferred_component_type)
21
- end
22
-
23
- private
24
-
25
- # this returns the element type
26
- # one of :input, :textarea, :select
27
- def infer_component_type
28
- return :select unless collection.nil?
29
-
30
- case inferred_db_type
31
- when :text, :json, :jsonb, :hstore
32
- :textarea
33
- else
34
- :input
35
- end
36
- end
37
-
38
- # this only applies when input_component is `:input`
39
- # resolves the type attribute of input components
40
- def infer_input_component_subtype(component)
41
- case inferred_db_type
42
- when :string
43
- infer_string_input_type(key)
44
- when :integer, :float, :decimal
45
- :number
46
- when :date
47
- :date
48
- when :datetime
49
- :datetime
50
- when :time
51
- :time
52
- when :boolean
53
- :checkbox
54
- else
55
- :text
56
- end
57
- end
58
-
59
- def infer_db_type
60
- if object.class.respond_to?(:columns_hash)
61
- # ActiveRecord object
62
- column = object.class.columns_hash[key.to_s]
63
- return column.type if column
64
- end
65
-
66
- if object.class.respond_to?(:attribute_types)
67
- # ActiveModel::Attributes
68
- custom_type = object.class.attribute_types[key.to_s]
69
- return custom_type.type if custom_type&.type
70
- end
71
-
72
- # Check if object responds to the key
73
- if object.respond_to?(key)
74
- # Fallback to inferring type from the value
75
- return infer_db_type_from_value(object.send(key))
76
- end
77
-
78
- # Default to string if we can't determine the type
79
- :string
80
- end
81
-
82
- def infer_db_type_from_value(value)
83
- case value
84
- when Integer
85
- :integer
86
- when Float
87
- :float
88
- when BigDecimal
89
- :decimal
90
- when TrueClass, FalseClass
91
- :boolean
92
- when Date
93
- :date
94
- when Time, DateTime
95
- :datetime
96
- else
97
- :string
98
- end
99
- end
100
-
101
- def infer_string_input_type(key)
102
- key = key.to_s.downcase
103
-
104
- return :password if is_password_field?
105
-
106
- custom_type = custom_string_input_type(key)
107
- return custom_type if custom_type
108
-
109
- if has_validators?
110
- infer_string_input_type_from_validations
111
- else
112
- :text
113
- end
114
- end
115
-
116
- def custom_string_input_type(key)
117
- custom_mappings = {
118
- /url$|^link|^site/ => :url,
119
- /^email/ => :email,
120
- /^search/ => :search,
121
- /phone|tel(ephone)?/ => :tel,
122
- /^time/ => :time,
123
- /^date/ => :date,
124
- /^number|_count$|_amount$/ => :number,
125
- /^color/ => :color
126
- }
127
-
128
- custom_mappings.each do |pattern, type|
129
- return type if key.match?(pattern)
130
- end
131
-
132
- nil
133
- end
134
-
135
- def infer_string_input_type_from_validations
136
- if attribute_validators.find { |v| v.kind == :numericality }
137
- :number
138
- elsif attribute_validators.find { |v| v.kind == :format && v.options[:with] == URI::MailTo::EMAIL_REGEXP }
139
- :email
140
- else
141
- :text
142
- end
143
- end
144
-
145
- def is_password_field?
146
- key = self.key.to_s.downcase
147
-
148
- exact_matches = ["password"]
149
- prefixes = ["encrypted_"]
150
- suffixes = ["_password", "_digest", "_hash"]
151
-
152
- exact_matches.include?(key) ||
153
- prefixes.any? { |prefix| key.start_with?(prefix) } ||
154
- suffixes.any? { |suffix| key.end_with?(suffix) }
155
- end
156
- end
157
- end
158
- end
159
- end
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Form
5
- module FieldOptions
6
- module Labels
7
- def label(label = nil)
8
- if label.nil?
9
- options[:label] = options.fetch(:label) { calculate_label }
10
- else
11
- options[:label] = label
12
- self
13
- end
14
- end
15
-
16
- private
17
-
18
- def calculate_label
19
- if object.class.respond_to?(:human_attribute_name)
20
- object.class.human_attribute_name(key.to_s, {base: object})
21
- else
22
- key.to_s.humanize
23
- end
24
- end
25
- end
26
- end
27
- end
28
- end
@@ -1,92 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Form
5
- module FieldOptions
6
- module MinMax
7
- def min(min_value = nil)
8
- if min_value.nil?
9
- options[:min] = options.fetch(:min) { calculate_min }
10
- else
11
- options[:min] = min_value
12
- self
13
- end
14
- end
15
-
16
- def max(max_value = nil)
17
- if max_value.nil?
18
- options[:max] = options.fetch(:max) { calculate_max }
19
- else
20
- options[:max] = max_value
21
- self
22
- end
23
- end
24
-
25
- def step
26
- 1 if min || max
27
- end
28
-
29
- private
30
-
31
- def calculate_min
32
- if (numericality_validator = find_numericality_validator)
33
- get_min_from_validator(numericality_validator)
34
- end
35
- end
36
-
37
- def calculate_max
38
- if (numericality_validator = find_numericality_validator)
39
- get_max_from_validator(numericality_validator)
40
- end
41
- end
42
-
43
- def find_numericality_validator
44
- find_validator(:numericality)
45
- end
46
-
47
- def get_min_from_validator(validator)
48
- options = validator.options
49
- min = if options.key?(:greater_than)
50
- {value: options[:greater_than], exclusive: true}
51
- elsif options.key?(:greater_than_or_equal_to)
52
- {value: options[:greater_than_or_equal_to], exclusive: false}
53
- end
54
- evaluate_and_adjust_min(min)
55
- end
56
-
57
- def get_max_from_validator(validator)
58
- options = validator.options
59
- max = if options.key?(:less_than)
60
- {value: options[:less_than], exclusive: true}
61
- elsif options.key?(:less_than_or_equal_to)
62
- {value: options[:less_than_or_equal_to], exclusive: false}
63
- end
64
- evaluate_and_adjust_max(max)
65
- end
66
-
67
- def evaluate_and_adjust_min(min)
68
- return nil unless min
69
-
70
- value = evaluate_numericality_validator_option(min[:value])
71
- min[:exclusive] ? value + 1 : value
72
- end
73
-
74
- def evaluate_and_adjust_max(max)
75
- return nil unless max
76
-
77
- value = evaluate_numericality_validator_option(max[:value])
78
- max[:exclusive] ? value - 1 : value
79
- end
80
-
81
- def evaluate_numericality_validator_option(option)
82
- case option
83
- when Proc
84
- option.arity.zero? ? option.call : option.call(object)
85
- else
86
- option
87
- end
88
- end
89
- end
90
- end
91
- end
92
- end