phlexi-form 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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.0"
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.0
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-15 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