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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d20cf0d6ccfcdf3cbb3a05a518b25a9b6176ef827e32f9cab47fddb43391f470
4
- data.tar.gz: '0788efe146100a6629f679d913a080ff7cc2039881477bc9ffda16dfe0f6dc9f'
3
+ metadata.gz: aa37fb3ecaded18bae5e7be4d2773c227acc970c588eceb276214f39597cf5cf
4
+ data.tar.gz: cc06f9e6d21d87e66efe7eb4a27fd1b2e9721b14f9dab5b1ab7df5c74ff58834
5
5
  SHA512:
6
- metadata.gz: 3e671e22b8daa6e5e243725f79302e26b9b1d5395d92a54013d0aed981bc9eb2dfd756d8214a69446e0328186b339dc21dd3e1853c5236270f048e974c4ae114
7
- data.tar.gz: d7441ae010f74f188baf43b6ac90e5cdd49b88563a22261e223b11d3c20de6135e5f610c0f278eaae28d49bc3627df3ec0b3012fe49d5e41626751d15200fd26
6
+ metadata.gz: d37efe7ef9526991cce1495180edeacab2322c499302dab5e1118731bf2410e708e1404a6dfc1c3bebaf7846366fa05c57acc94669cc6caf60a798159ff9bf8b
7
+ data.tar.gz: ec2a45b47b89e0215cf034584d7d1d7cd5f60aa3fc640447492ba184a01069150879ddb101e7616b71fbde770222701df50ac96f0361e7a848967bc17c405885
data/README.md CHANGED
@@ -159,7 +159,7 @@ Phlexi::Form supports theming through a flexible theming system:
159
159
 
160
160
  ```ruby
161
161
  class ThemedForm < Phlexi::Form::Base
162
- class FieldBuilder < FieldBuilder
162
+ class Builder < Builder
163
163
  private
164
164
 
165
165
  def default_theme
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- phlexi-form (0.3.0.rc1)
4
+ phlexi-form (0.3.1)
5
5
  activesupport
6
6
  phlex (~> 1.11)
7
7
  zeitwerk
@@ -9,32 +9,34 @@ PATH
9
9
  GEM
10
10
  remote: https://rubygems.org/
11
11
  specs:
12
- actionpack (7.1.3.4)
13
- actionview (= 7.1.3.4)
14
- activesupport (= 7.1.3.4)
12
+ actionpack (7.2.1)
13
+ actionview (= 7.2.1)
14
+ activesupport (= 7.2.1)
15
15
  nokogiri (>= 1.8.5)
16
16
  racc
17
- rack (>= 2.2.4)
17
+ rack (>= 2.2.4, < 3.2)
18
18
  rack-session (>= 1.0.1)
19
19
  rack-test (>= 0.6.3)
20
20
  rails-dom-testing (~> 2.2)
21
21
  rails-html-sanitizer (~> 1.6)
22
- actionview (7.1.3.4)
23
- activesupport (= 7.1.3.4)
22
+ useragent (~> 0.16)
23
+ actionview (7.2.1)
24
+ activesupport (= 7.2.1)
24
25
  builder (~> 3.1)
25
26
  erubi (~> 1.11)
26
27
  rails-dom-testing (~> 2.2)
27
28
  rails-html-sanitizer (~> 1.6)
28
- activesupport (7.1.3.4)
29
+ activesupport (7.2.1)
29
30
  base64
30
31
  bigdecimal
31
- concurrent-ruby (~> 1.0, >= 1.0.2)
32
+ concurrent-ruby (~> 1.0, >= 1.3.1)
32
33
  connection_pool (>= 2.2.5)
33
34
  drb
34
35
  i18n (>= 1.6, < 2)
36
+ logger (>= 1.4.2)
35
37
  minitest (>= 5.1)
36
- mutex_m
37
- tzinfo (~> 2.0)
38
+ securerandom (>= 0.3)
39
+ tzinfo (~> 2.0, >= 2.0.5)
38
40
  addressable (2.8.7)
39
41
  public_suffix (>= 2.0.2, < 7.0)
40
42
  ansi (1.5.0)
@@ -48,7 +50,7 @@ GEM
48
50
  builder (3.3.0)
49
51
  bundle-audit (0.1.0)
50
52
  bundler-audit
51
- bundler-audit (0.9.1)
53
+ bundler-audit (0.9.2)
52
54
  bundler (>= 1.2.0, < 3)
53
55
  thor (~> 1.0)
54
56
  capybara (3.40.0)
@@ -64,7 +66,7 @@ GEM
64
66
  activesupport (>= 3.0.0)
65
67
  railties (>= 3.0.0)
66
68
  thor (>= 0.14.6)
67
- concurrent-ruby (1.3.3)
69
+ concurrent-ruby (1.3.4)
68
70
  connection_pool (2.4.1)
69
71
  crass (1.0.6)
70
72
  drb (2.2.1)
@@ -78,22 +80,22 @@ GEM
78
80
  json (2.7.2)
79
81
  language_server-protocol (3.17.0.3)
80
82
  lint_roller (1.1.0)
83
+ logger (1.6.1)
81
84
  loofah (2.22.0)
82
85
  crass (~> 1.0.2)
83
86
  nokogiri (>= 1.12.0)
84
87
  matrix (0.4.2)
85
88
  mini_mime (1.1.5)
86
- minitest (5.24.1)
89
+ minitest (5.25.1)
87
90
  minitest-reporters (1.7.1)
88
91
  ansi
89
92
  builder
90
93
  minitest (>= 5.0)
91
94
  ruby-progressbar
92
- mutex_m (0.2.0)
93
95
  nokogiri (1.16.7-x86_64-darwin)
94
96
  racc (~> 1.4)
95
- parallel (1.25.1)
96
- parser (3.3.4.0)
97
+ parallel (1.26.3)
98
+ parser (3.3.5.0)
97
99
  ast (~> 2.4.1)
98
100
  racc
99
101
  phlex (1.11.0)
@@ -119,10 +121,10 @@ GEM
119
121
  rails-html-sanitizer (1.6.0)
120
122
  loofah (~> 2.21)
121
123
  nokogiri (~> 1.14)
122
- railties (7.1.3.4)
123
- actionpack (= 7.1.3.4)
124
- activesupport (= 7.1.3.4)
125
- irb
124
+ railties (7.2.1)
125
+ actionpack (= 7.2.1)
126
+ activesupport (= 7.2.1)
127
+ irb (~> 1.13)
126
128
  rackup (>= 1.0.0)
127
129
  rake (>= 12.2)
128
130
  thor (~> 1.0, >= 1.2.2)
@@ -132,31 +134,31 @@ GEM
132
134
  rdoc (6.7.0)
133
135
  psych (>= 4.0.0)
134
136
  regexp_parser (2.9.2)
135
- reline (0.5.9)
137
+ reline (0.5.10)
136
138
  io-console (~> 0.5)
137
- rexml (3.3.2)
138
- strscan
139
- rubocop (1.64.1)
139
+ rexml (3.3.7)
140
+ rubocop (1.65.1)
140
141
  json (~> 2.3)
141
142
  language_server-protocol (>= 3.17.0)
142
143
  parallel (~> 1.10)
143
144
  parser (>= 3.3.0.2)
144
145
  rainbow (>= 2.2.2, < 4.0)
145
- regexp_parser (>= 1.8, < 3.0)
146
+ regexp_parser (>= 2.4, < 3.0)
146
147
  rexml (>= 3.2.5, < 4.0)
147
148
  rubocop-ast (>= 1.31.1, < 2.0)
148
149
  ruby-progressbar (~> 1.7)
149
150
  unicode-display_width (>= 2.4.0, < 3.0)
150
- rubocop-ast (1.31.3)
151
+ rubocop-ast (1.32.3)
151
152
  parser (>= 3.3.1.0)
152
153
  rubocop-performance (1.21.1)
153
154
  rubocop (>= 1.48.1, < 2.0)
154
155
  rubocop-ast (>= 1.31.1, < 2.0)
155
156
  ruby-progressbar (1.13.0)
156
- standard (1.39.2)
157
+ securerandom (0.3.1)
158
+ standard (1.40.0)
157
159
  language_server-protocol (~> 3.17.0.2)
158
160
  lint_roller (~> 1.0)
159
- rubocop (~> 1.64.0)
161
+ rubocop (~> 1.65.0)
160
162
  standard-custom (~> 1.0.0)
161
163
  standard-performance (~> 1.4)
162
164
  standard-custom (1.0.2)
@@ -166,15 +168,15 @@ GEM
166
168
  lint_roller (~> 1.1)
167
169
  rubocop-performance (~> 1.21.0)
168
170
  stringio (3.1.1)
169
- strscan (3.1.0)
170
- thor (1.3.1)
171
+ thor (1.3.2)
171
172
  tzinfo (2.0.6)
172
173
  concurrent-ruby (~> 1.0)
173
174
  unicode-display_width (2.5.0)
175
+ useragent (0.16.10)
174
176
  webrick (1.8.1)
175
177
  xpath (3.2.0)
176
178
  nokogiri (~> 1.8)
177
- zeitwerk (2.6.17)
179
+ zeitwerk (2.6.18)
178
180
 
179
181
  PLATFORMS
180
182
  x86_64-darwin
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- phlexi-form (0.3.0.rc1)
4
+ phlexi-form (0.3.1)
5
5
  activesupport
6
6
  phlex (~> 1.11)
7
7
  zeitwerk
@@ -96,7 +96,7 @@ GEM
96
96
  builder (3.3.0)
97
97
  bundle-audit (0.1.0)
98
98
  bundler-audit
99
- bundler-audit (0.9.1)
99
+ bundler-audit (0.9.2)
100
100
  bundler (>= 1.2.0, < 3)
101
101
  thor (~> 1.0)
102
102
  capybara (3.40.0)
@@ -112,7 +112,7 @@ GEM
112
112
  activesupport (>= 3.0.0)
113
113
  railties (>= 3.0.0)
114
114
  thor (>= 0.14.6)
115
- concurrent-ruby (1.3.3)
115
+ concurrent-ruby (1.3.4)
116
116
  connection_pool (2.4.1)
117
117
  crass (1.0.6)
118
118
  date (3.3.4)
@@ -140,14 +140,14 @@ GEM
140
140
  marcel (1.0.4)
141
141
  matrix (0.4.2)
142
142
  mini_mime (1.1.5)
143
- minitest (5.24.1)
143
+ minitest (5.25.1)
144
144
  minitest-reporters (1.7.1)
145
145
  ansi
146
146
  builder
147
147
  minitest (>= 5.0)
148
148
  ruby-progressbar
149
149
  mutex_m (0.2.0)
150
- net-imap (0.4.14)
150
+ net-imap (0.4.16)
151
151
  date
152
152
  net-protocol
153
153
  net-pop (0.1.2)
@@ -159,8 +159,8 @@ GEM
159
159
  nio4r (2.7.3)
160
160
  nokogiri (1.16.7-x86_64-darwin)
161
161
  racc (~> 1.4)
162
- parallel (1.25.1)
163
- parser (3.3.4.0)
162
+ parallel (1.26.3)
163
+ parser (3.3.5.0)
164
164
  ast (~> 2.4.1)
165
165
  racc
166
166
  phlex (1.11.0)
@@ -213,32 +213,31 @@ GEM
213
213
  rdoc (6.7.0)
214
214
  psych (>= 4.0.0)
215
215
  regexp_parser (2.9.2)
216
- reline (0.5.9)
216
+ reline (0.5.10)
217
217
  io-console (~> 0.5)
218
- rexml (3.3.2)
219
- strscan
220
- rubocop (1.64.1)
218
+ rexml (3.3.7)
219
+ rubocop (1.65.1)
221
220
  json (~> 2.3)
222
221
  language_server-protocol (>= 3.17.0)
223
222
  parallel (~> 1.10)
224
223
  parser (>= 3.3.0.2)
225
224
  rainbow (>= 2.2.2, < 4.0)
226
- regexp_parser (>= 1.8, < 3.0)
225
+ regexp_parser (>= 2.4, < 3.0)
227
226
  rexml (>= 3.2.5, < 4.0)
228
227
  rubocop-ast (>= 1.31.1, < 2.0)
229
228
  ruby-progressbar (~> 1.7)
230
229
  unicode-display_width (>= 2.4.0, < 3.0)
231
- rubocop-ast (1.31.3)
230
+ rubocop-ast (1.32.3)
232
231
  parser (>= 3.3.1.0)
233
232
  rubocop-performance (1.21.1)
234
233
  rubocop (>= 1.48.1, < 2.0)
235
234
  rubocop-ast (>= 1.31.1, < 2.0)
236
235
  ruby-progressbar (1.13.0)
237
236
  sqlite3 (1.7.3-x86_64-darwin)
238
- standard (1.39.2)
237
+ standard (1.40.0)
239
238
  language_server-protocol (~> 3.17.0.2)
240
239
  lint_roller (~> 1.0)
241
- rubocop (~> 1.64.0)
240
+ rubocop (~> 1.65.0)
242
241
  standard-custom (~> 1.0.0)
243
242
  standard-performance (~> 1.4)
244
243
  standard-custom (1.0.2)
@@ -248,8 +247,7 @@ GEM
248
247
  lint_roller (~> 1.1)
249
248
  rubocop-performance (~> 1.21.0)
250
249
  stringio (3.1.1)
251
- strscan (3.1.0)
252
- thor (1.3.1)
250
+ thor (1.3.2)
253
251
  timeout (0.4.1)
254
252
  tzinfo (2.0.6)
255
253
  concurrent-ruby (~> 1.0)
@@ -260,7 +258,7 @@ GEM
260
258
  websocket-extensions (0.1.5)
261
259
  xpath (3.2.0)
262
260
  nokogiri (~> 1.8)
263
- zeitwerk (2.6.17)
261
+ zeitwerk (2.6.18)
264
262
 
265
263
  PLATFORMS
266
264
  x86_64-darwin
@@ -17,10 +17,18 @@ module Phlexi
17
17
  #
18
18
  # @attr_reader [Symbol] key The form's key, derived from the record or explicitly set
19
19
  # @attr_reader [ActiveModel::Model, nil] object The form's associated object
20
- class Base < COMPONENT_BASE
21
- class Namespace < Structure::Namespace; end
20
+ class Base < Phlexi::Form::HTML
21
+ class Namespace < Phlexi::Form::Structure::Namespace; end
22
22
 
23
- class FieldBuilder < Structure::FieldBuilder; end
23
+ class Builder < Phlexi::Form::Builder; end
24
+
25
+ def self.inline(*, **, &block)
26
+ raise ArgumentError, "block is required" unless block
27
+
28
+ new(*, **) do |f|
29
+ f.instance_exec(&block)
30
+ end
31
+ end
24
32
 
25
33
  attr_reader :key, :object
26
34
 
@@ -39,7 +47,6 @@ module Phlexi
39
47
  def initialize(record, action: nil, method: nil, attributes: {}, **options)
40
48
  @form_action = action
41
49
  @form_method = method
42
- @form_class = options.delete(:class)
43
50
  @attributes = attributes
44
51
  @namespace_klass = options.delete(:namespace_klass) || default_namespace_klass
45
52
  @builder_klass = options.delete(:builder_klass) || default_builder_klass
@@ -53,8 +60,8 @@ module Phlexi
53
60
  # Renders the form template.
54
61
  #
55
62
  # @return [void]
56
- def view_template
57
- form_tag { form_template }
63
+ def view_template(&)
64
+ form_tag { form_template(&) }
58
65
  end
59
66
 
60
67
  # Executes the form's content block.
@@ -62,7 +69,7 @@ module Phlexi
62
69
  #
63
70
  # @return [void]
64
71
  def form_template
65
- instance_exec(&@_content_block) if @_content_block
72
+ yield if block_given?
66
73
  end
67
74
 
68
75
  def extract_input(params)
@@ -117,7 +124,9 @@ module Phlexi
117
124
  # Retrieves the form's CSS classes.
118
125
  #
119
126
  # @return [String] The form's CSS classes
120
- attr_reader :form_class
127
+ def form_class
128
+ themed(:base, nil)
129
+ end
121
130
 
122
131
  # Renders the form tag with its contents.
123
132
  #
@@ -236,7 +245,7 @@ module Phlexi
236
245
  end
237
246
 
238
247
  def default_builder_klass
239
- self.class::FieldBuilder
248
+ self.class::Builder
240
249
  end
241
250
  end
242
251
  end
@@ -0,0 +1,297 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlexi
4
+ module Form
5
+ # Builder class is responsible for building form fields with various options and components.
6
+ class Builder < Phlexi::Field::Builder
7
+ include Phlexi::Form::HTML::Behaviour
8
+ include Options::Validators
9
+ include Options::InferredTypes
10
+ include Options::Errors
11
+ include Options::Collection
12
+ include Options::Hints
13
+ include Options::Required
14
+ include Options::Autofocus
15
+ include Options::Disabled
16
+ include Options::Readonly
17
+ include Options::Length
18
+ include Options::Max
19
+ include Options::Min
20
+ include Options::Pattern
21
+ include Options::Limit
22
+ include Options::Step
23
+
24
+ class FieldCollection < Phlexi::Form::Structure::FieldCollection; end
25
+
26
+ attr_reader :input_attributes
27
+
28
+ # Initializes a new Builder instance.
29
+ #
30
+ # @param key [Symbol, String] The key for the field.
31
+ # @param parent [Structure::Namespace] The parent object.
32
+ # @param object [Object, nil] The associated object.
33
+ # @param value [Object] The initial value for the field.
34
+ # @param input_attributes [Hash] Default attributes to apply to input fields.
35
+ # @param options [Hash] Additional options for the field.
36
+ def initialize(*, input_attributes: {}, **)
37
+ super(*, **)
38
+
39
+ @input_attributes = input_attributes
40
+ end
41
+
42
+ # Creates an input tag for the field.
43
+ #
44
+ # @param attributes [Hash] Additional attributes for the input.
45
+ # @return [Components::Input] The input component.
46
+ def input_tag(**, &)
47
+ create_component(Components::Input, :input, **, &)
48
+ end
49
+
50
+ def string_tag(**, &)
51
+ input_tag(type: :text, theme: :string, **, &)
52
+ end
53
+
54
+ def number_tag(**, &)
55
+ input_tag(type: :number, theme: :number, **, &)
56
+ end
57
+
58
+ def date_tag(**, &)
59
+ input_tag(type: :date, theme: :date, **, &)
60
+ end
61
+
62
+ def time_tag(**, &)
63
+ input_tag(type: :time, theme: :time, **, &)
64
+ end
65
+
66
+ def datetime_local_tag(**, &)
67
+ input_tag(type: :"datetime-local", theme: :datetime, **, &)
68
+ end
69
+ alias_method :datetime_tag, :datetime_local_tag
70
+
71
+ def email_tag(**, &)
72
+ input_tag(type: :email, theme: :email, **, &)
73
+ end
74
+
75
+ def password_tag(**, &)
76
+ input_tag(type: :password, theme: :password, **, &)
77
+ end
78
+
79
+ def phone_tag(**, &)
80
+ input_tag(type: :tel, theme: :phone, **, &)
81
+ end
82
+
83
+ def color_tag(**, &)
84
+ input_tag(type: :color, theme: :color, **, &)
85
+ end
86
+
87
+ def url_tag(**, &)
88
+ input_tag(type: :url, theme: :url, **, &)
89
+ end
90
+
91
+ def search_tag(**, &)
92
+ input_tag(type: :search, theme: :search, **, &)
93
+ end
94
+
95
+ # Creates a checkbox tag for the field.
96
+ #
97
+ # @param attributes [Hash] Additional attributes for the checkbox.
98
+ # @return [Components::Checkbox] The checkbox component.
99
+ def checkbox_tag(**, &)
100
+ create_component(Components::Checkbox, :checkbox, **, &)
101
+ end
102
+
103
+ def boolean_tag(**, &)
104
+ checkbox_tag(**, theme: :boolean, &)
105
+ end
106
+
107
+ def file_input_tag(**, &)
108
+ create_component(Components::FileInput, :file, **, &)
109
+ end
110
+ alias_method :file_tag, :file_input_tag
111
+
112
+ # Creates collection checkboxes for the field.
113
+ #
114
+ # @param attributes [Hash] Additional attributes for the collection checkboxes.
115
+ # @yield [block] The block to be executed for each checkbox.
116
+ # @return [Components::CollectionCheckboxes] The collection checkboxes component.
117
+ def collection_checkboxes_tag(**, &)
118
+ create_component(Components::CollectionCheckboxes, :collection_checkboxes, **, &)
119
+ end
120
+
121
+ # Creates a radio button tag for the field.
122
+ #
123
+ # @param attributes [Hash] Additional attributes for the radio button.
124
+ # @return [Components::RadioButton] The radio button component.
125
+ def radio_button_tag(**, &)
126
+ create_component(Components::RadioButton, :radio, **, &)
127
+ end
128
+
129
+ # Creates collection radio buttons for the field.
130
+ #
131
+ # @param attributes [Hash] Additional attributes for the collection radio buttons.
132
+ # @yield [block] The block to be executed for each radio button.
133
+ # @return [Components::CollectionRadioButtons] The collection radio buttons component.
134
+ def collection_radio_buttons_tag(**, &)
135
+ create_component(Components::CollectionRadioButtons, :collection_radio_buttons, **, &)
136
+ end
137
+
138
+ # Creates a textarea tag for the field.
139
+ #
140
+ # @param attributes [Hash] Additional attributes for the textarea.
141
+ # @return [Components::Textarea] The textarea component.
142
+ def textarea_tag(**, &)
143
+ create_component(Components::Textarea, :textarea, **, &)
144
+ end
145
+ alias_method :text_tag, :textarea_tag
146
+
147
+ def hstore_tag(**, &)
148
+ @value = @value.to_s.tr("{}", "")
149
+ textarea_tag(**, theme: :hstore, &)
150
+ end
151
+
152
+ # Creates a select tag for the field.
153
+ #
154
+ # @param attributes [Hash] Additional attributes for the select.
155
+ # @return [Components::Select] The select component.
156
+ def select_tag(**, &)
157
+ create_component(Components::Select, :select, **, &)
158
+ end
159
+
160
+ def belongs_to_tag(**options, &)
161
+ options.fetch(:input_param) {
162
+ options[:input_param] = if association_reflection.respond_to?(:options) && association_reflection.options[:foreign_key]
163
+ association_reflection.options[:foreign_key]
164
+ else
165
+ :"#{association_reflection.name}_id"
166
+ end
167
+ }
168
+ select_tag(**options, &)
169
+ end
170
+
171
+ def polymorphic_belongs_to_tag(**, &)
172
+ # TODO: this requires a grouped_select component
173
+ # see: Plutonium::Core::Fields::Inputs::PolymorphicBelongsToAssociationInput
174
+ raise NotImplementedError, "polymorphic belongs_to associations are not YET supported"
175
+ end
176
+
177
+ def has_one_tag(**, &)
178
+ raise NotImplementedError, "has_one associations are NOT supported"
179
+ end
180
+
181
+ def has_many_tag(**options, &)
182
+ options.fetch(:input_param) {
183
+ options[:input_param] = :"#{association_reflection.name.to_s.singularize}_ids"
184
+ }
185
+
186
+ select_tag(**options, &)
187
+ end
188
+
189
+ def input_array_tag(**, &)
190
+ create_component(Components::InputArray, :array, **, &)
191
+ end
192
+
193
+ # Creates a label tag for the field.
194
+ #
195
+ # @param attributes [Hash] Additional attributes for the label.
196
+ # @return [Components::Label] The label component.
197
+ def label_tag(**, &)
198
+ create_component(Components::Label, :label, **, &)
199
+ end
200
+
201
+ # Creates a hint tag for the field.
202
+ #
203
+ # @param attributes [Hash] Additional attributes for the hint.
204
+ # @return [Components::Hint] The hint component.
205
+ def hint_tag(**, &)
206
+ create_component(Components::Hint, :hint, **, &)
207
+ end
208
+
209
+ # Creates an error tag for the field.
210
+ #
211
+ # @param attributes [Hash] Additional attributes for the error.
212
+ # @return [Components::Error] The error component.
213
+ def error_tag(**, &)
214
+ create_component(Components::Error, :error, **, &)
215
+ end
216
+
217
+ # Creates a full error tag for the field.
218
+ #
219
+ # @param attributes [Hash] Additional attributes for the full error.
220
+ # @return [Components::FullError] The full error component.
221
+ def full_error_tag(**, &)
222
+ create_component(Components::FullError, :full_error, **, &)
223
+ end
224
+
225
+ # Wraps the field with additional markup.
226
+ #
227
+ # @param inner [Hash] Attributes for the inner wrapper.
228
+ # @param attributes [Hash] Additional attributes for the wrapper.
229
+ # @yield [block] The block to be executed within the wrapper.
230
+ # @return [Components::Wrapper] The wrapper component.
231
+ def wrapped(inner: {}, **attributes, &)
232
+ attributes = apply_component_theme(attributes, :wrapper)
233
+ inner = apply_component_theme(inner, :inner_wrapper)
234
+ Components::Wrapper.new(self, inner: inner, **attributes, &)
235
+ end
236
+
237
+ # Creates a submit button
238
+ #
239
+ # @param attributes [Hash] Additional attributes for the submit.
240
+ # @return [Components::SubmitButton] The submit button component.
241
+ def submit_button_tag(**, &)
242
+ create_component(Components::SubmitButton, :submit_button, **, &)
243
+ end
244
+
245
+ def extract_input(params)
246
+ raise "field##{dom.name} did not define an input component" unless @field_input_extractor
247
+
248
+ @field_input_extractor.extract_input(params)
249
+ end
250
+
251
+ protected
252
+
253
+ def create_component(component_class, theme_key, **attributes, &)
254
+ attributes = mix(input_attributes, attributes) if component_class.include?(Phlexi::Form::Components::Concerns::HandlesInput)
255
+ component = component_class.new(self, **apply_component_theme(attributes, theme_key), &)
256
+ if component_class.include?(Components::Concerns::ExtractsInput)
257
+ raise "input component already defined: #{@field_input_extractor.inspect}" if @field_input_extractor
258
+
259
+ @field_input_extractor = component
260
+ end
261
+
262
+ component
263
+ end
264
+
265
+ def apply_component_theme(attributes, theme_key)
266
+ return attributes if attributes.key?(:class!)
267
+
268
+ theme_key = attributes.delete(:theme) || theme_key
269
+ mix({class: themed(theme_key, self)}, attributes)
270
+ end
271
+
272
+ def determine_initial_value(value)
273
+ return value unless value == NIL_VALUE
274
+
275
+ determine_value_from_association || super
276
+ end
277
+
278
+ def determine_value_from_object
279
+ object.respond_to?(key) ? object.public_send(key) : nil
280
+ end
281
+
282
+ def determine_value_from_association
283
+ return nil unless association_reflection.present?
284
+
285
+ value = object.public_send(key)
286
+ case association_reflection.macro
287
+ when :has_many, :has_and_belongs_to_many
288
+ value&.map { |v| v.public_send(association_reflection.klass.primary_key) }
289
+ when :belongs_to, :has_one
290
+ value&.public_send(association_reflection.klass.primary_key)
291
+ else
292
+ raise ArgumentError, "Unsupported association type: #{association_reflection.macro}"
293
+ end
294
+ end
295
+ end
296
+ end
297
+ end
@@ -3,7 +3,7 @@
3
3
  module Phlexi
4
4
  module Form
5
5
  module Components
6
- class Base < COMPONENT_BASE
6
+ class Base < Phlexi::Form::HTML
7
7
  attr_reader :field, :attributes
8
8
 
9
9
  def initialize(field, **attributes)