phlexi-field 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,282 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ phlexi-form (0.3.0.rc1)
5
+ activesupport
6
+ phlex (~> 1.11)
7
+ zeitwerk
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ actioncable (7.1.3.4)
13
+ actionpack (= 7.1.3.4)
14
+ activesupport (= 7.1.3.4)
15
+ nio4r (~> 2.0)
16
+ websocket-driver (>= 0.6.1)
17
+ zeitwerk (~> 2.6)
18
+ actionmailbox (7.1.3.4)
19
+ actionpack (= 7.1.3.4)
20
+ activejob (= 7.1.3.4)
21
+ activerecord (= 7.1.3.4)
22
+ activestorage (= 7.1.3.4)
23
+ activesupport (= 7.1.3.4)
24
+ mail (>= 2.7.1)
25
+ net-imap
26
+ net-pop
27
+ net-smtp
28
+ actionmailer (7.1.3.4)
29
+ actionpack (= 7.1.3.4)
30
+ actionview (= 7.1.3.4)
31
+ activejob (= 7.1.3.4)
32
+ activesupport (= 7.1.3.4)
33
+ mail (~> 2.5, >= 2.5.4)
34
+ net-imap
35
+ net-pop
36
+ net-smtp
37
+ rails-dom-testing (~> 2.2)
38
+ actionpack (7.1.3.4)
39
+ actionview (= 7.1.3.4)
40
+ activesupport (= 7.1.3.4)
41
+ nokogiri (>= 1.8.5)
42
+ racc
43
+ rack (>= 2.2.4)
44
+ rack-session (>= 1.0.1)
45
+ rack-test (>= 0.6.3)
46
+ rails-dom-testing (~> 2.2)
47
+ rails-html-sanitizer (~> 1.6)
48
+ actiontext (7.1.3.4)
49
+ actionpack (= 7.1.3.4)
50
+ activerecord (= 7.1.3.4)
51
+ activestorage (= 7.1.3.4)
52
+ activesupport (= 7.1.3.4)
53
+ globalid (>= 0.6.0)
54
+ nokogiri (>= 1.8.5)
55
+ actionview (7.1.3.4)
56
+ activesupport (= 7.1.3.4)
57
+ builder (~> 3.1)
58
+ erubi (~> 1.11)
59
+ rails-dom-testing (~> 2.2)
60
+ rails-html-sanitizer (~> 1.6)
61
+ activejob (7.1.3.4)
62
+ activesupport (= 7.1.3.4)
63
+ globalid (>= 0.3.6)
64
+ activemodel (7.1.3.4)
65
+ activesupport (= 7.1.3.4)
66
+ activerecord (7.1.3.4)
67
+ activemodel (= 7.1.3.4)
68
+ activesupport (= 7.1.3.4)
69
+ timeout (>= 0.4.0)
70
+ activestorage (7.1.3.4)
71
+ actionpack (= 7.1.3.4)
72
+ activejob (= 7.1.3.4)
73
+ activerecord (= 7.1.3.4)
74
+ activesupport (= 7.1.3.4)
75
+ marcel (~> 1.0)
76
+ activesupport (7.1.3.4)
77
+ base64
78
+ bigdecimal
79
+ concurrent-ruby (~> 1.0, >= 1.0.2)
80
+ connection_pool (>= 2.2.5)
81
+ drb
82
+ i18n (>= 1.6, < 2)
83
+ minitest (>= 5.1)
84
+ mutex_m
85
+ tzinfo (~> 2.0)
86
+ addressable (2.8.7)
87
+ public_suffix (>= 2.0.2, < 7.0)
88
+ ansi (1.5.0)
89
+ appraisal (2.5.0)
90
+ bundler
91
+ rake
92
+ thor (>= 0.14.0)
93
+ ast (2.4.2)
94
+ base64 (0.2.0)
95
+ bigdecimal (3.1.8)
96
+ builder (3.3.0)
97
+ bundle-audit (0.1.0)
98
+ bundler-audit
99
+ bundler-audit (0.9.1)
100
+ bundler (>= 1.2.0, < 3)
101
+ thor (~> 1.0)
102
+ capybara (3.40.0)
103
+ addressable
104
+ matrix
105
+ mini_mime (>= 0.1.3)
106
+ nokogiri (~> 1.11)
107
+ rack (>= 1.6.0)
108
+ rack-test (>= 0.6.3)
109
+ regexp_parser (>= 1.5, < 3.0)
110
+ xpath (~> 3.2)
111
+ combustion (1.5.0)
112
+ activesupport (>= 3.0.0)
113
+ railties (>= 3.0.0)
114
+ thor (>= 0.14.6)
115
+ concurrent-ruby (1.3.3)
116
+ connection_pool (2.4.1)
117
+ crass (1.0.6)
118
+ date (3.3.4)
119
+ drb (2.2.1)
120
+ erubi (1.13.0)
121
+ globalid (1.2.1)
122
+ activesupport (>= 6.1)
123
+ i18n (1.14.5)
124
+ concurrent-ruby (~> 1.0)
125
+ io-console (0.7.2)
126
+ irb (1.14.0)
127
+ rdoc (>= 4.0.0)
128
+ reline (>= 0.4.2)
129
+ json (2.7.2)
130
+ language_server-protocol (3.17.0.3)
131
+ lint_roller (1.1.0)
132
+ loofah (2.22.0)
133
+ crass (~> 1.0.2)
134
+ nokogiri (>= 1.12.0)
135
+ mail (2.8.1)
136
+ mini_mime (>= 0.1.1)
137
+ net-imap
138
+ net-pop
139
+ net-smtp
140
+ marcel (1.0.4)
141
+ matrix (0.4.2)
142
+ mini_mime (1.1.5)
143
+ minitest (5.24.1)
144
+ minitest-reporters (1.7.1)
145
+ ansi
146
+ builder
147
+ minitest (>= 5.0)
148
+ ruby-progressbar
149
+ mutex_m (0.2.0)
150
+ net-imap (0.4.14)
151
+ date
152
+ net-protocol
153
+ net-pop (0.1.2)
154
+ net-protocol
155
+ net-protocol (0.2.2)
156
+ timeout
157
+ net-smtp (0.5.0)
158
+ net-protocol
159
+ nio4r (2.7.3)
160
+ nokogiri (1.16.7-x86_64-darwin)
161
+ racc (~> 1.4)
162
+ parallel (1.25.1)
163
+ parser (3.3.4.0)
164
+ ast (~> 2.4.1)
165
+ racc
166
+ phlex (1.11.0)
167
+ phlex-testing-capybara (0.1.0)
168
+ capybara (~> 3.38)
169
+ phlex (>= 0.5)
170
+ psych (5.1.2)
171
+ stringio
172
+ public_suffix (6.0.1)
173
+ racc (1.8.1)
174
+ rack (3.1.7)
175
+ rack-session (2.0.0)
176
+ rack (>= 3.0.0)
177
+ rack-test (2.1.0)
178
+ rack (>= 1.3)
179
+ rackup (2.1.0)
180
+ rack (>= 3)
181
+ webrick (~> 1.8)
182
+ rails (7.1.3.4)
183
+ actioncable (= 7.1.3.4)
184
+ actionmailbox (= 7.1.3.4)
185
+ actionmailer (= 7.1.3.4)
186
+ actionpack (= 7.1.3.4)
187
+ actiontext (= 7.1.3.4)
188
+ actionview (= 7.1.3.4)
189
+ activejob (= 7.1.3.4)
190
+ activemodel (= 7.1.3.4)
191
+ activerecord (= 7.1.3.4)
192
+ activestorage (= 7.1.3.4)
193
+ activesupport (= 7.1.3.4)
194
+ bundler (>= 1.15.0)
195
+ railties (= 7.1.3.4)
196
+ rails-dom-testing (2.2.0)
197
+ activesupport (>= 5.0.0)
198
+ minitest
199
+ nokogiri (>= 1.6)
200
+ rails-html-sanitizer (1.6.0)
201
+ loofah (~> 2.21)
202
+ nokogiri (~> 1.14)
203
+ railties (7.1.3.4)
204
+ actionpack (= 7.1.3.4)
205
+ activesupport (= 7.1.3.4)
206
+ irb
207
+ rackup (>= 1.0.0)
208
+ rake (>= 12.2)
209
+ thor (~> 1.0, >= 1.2.2)
210
+ zeitwerk (~> 2.6)
211
+ rainbow (3.1.1)
212
+ rake (13.2.1)
213
+ rdoc (6.7.0)
214
+ psych (>= 4.0.0)
215
+ regexp_parser (2.9.2)
216
+ reline (0.5.9)
217
+ io-console (~> 0.5)
218
+ rexml (3.3.2)
219
+ strscan
220
+ rubocop (1.64.1)
221
+ json (~> 2.3)
222
+ language_server-protocol (>= 3.17.0)
223
+ parallel (~> 1.10)
224
+ parser (>= 3.3.0.2)
225
+ rainbow (>= 2.2.2, < 4.0)
226
+ regexp_parser (>= 1.8, < 3.0)
227
+ rexml (>= 3.2.5, < 4.0)
228
+ rubocop-ast (>= 1.31.1, < 2.0)
229
+ ruby-progressbar (~> 1.7)
230
+ unicode-display_width (>= 2.4.0, < 3.0)
231
+ rubocop-ast (1.31.3)
232
+ parser (>= 3.3.1.0)
233
+ rubocop-performance (1.21.1)
234
+ rubocop (>= 1.48.1, < 2.0)
235
+ rubocop-ast (>= 1.31.1, < 2.0)
236
+ ruby-progressbar (1.13.0)
237
+ sqlite3 (1.7.3-x86_64-darwin)
238
+ standard (1.39.2)
239
+ language_server-protocol (~> 3.17.0.2)
240
+ lint_roller (~> 1.0)
241
+ rubocop (~> 1.64.0)
242
+ standard-custom (~> 1.0.0)
243
+ standard-performance (~> 1.4)
244
+ standard-custom (1.0.2)
245
+ lint_roller (~> 1.0)
246
+ rubocop (~> 1.50)
247
+ standard-performance (1.4.0)
248
+ lint_roller (~> 1.1)
249
+ rubocop-performance (~> 1.21.0)
250
+ stringio (3.1.1)
251
+ strscan (3.1.0)
252
+ thor (1.3.1)
253
+ timeout (0.4.1)
254
+ tzinfo (2.0.6)
255
+ concurrent-ruby (~> 1.0)
256
+ unicode-display_width (2.5.0)
257
+ webrick (1.8.1)
258
+ websocket-driver (0.7.6)
259
+ websocket-extensions (>= 0.1.0)
260
+ websocket-extensions (0.1.5)
261
+ xpath (3.2.0)
262
+ nokogiri (~> 1.8)
263
+ zeitwerk (2.6.17)
264
+
265
+ PLATFORMS
266
+ x86_64-darwin
267
+
268
+ DEPENDENCIES
269
+ appraisal
270
+ bundle-audit
271
+ combustion
272
+ minitest
273
+ minitest-reporters
274
+ phlex-testing-capybara
275
+ phlexi-form!
276
+ rails (~> 7.1.3, >= 7.1.3.4)
277
+ rake
278
+ sqlite3 (~> 1.4)
279
+ standard
280
+
281
+ BUNDLED WITH
282
+ 2.5.16
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "phlex"
4
+
5
+ module Phlexi
6
+ module Field
7
+ # Builder class is responsible for building fields with various options and components.
8
+ #
9
+ # @attr_reader [Structure::DOM] dom The DOM structure for the field.
10
+ # @attr_reader [Hash] options Options for the field.
11
+ # @attr_reader [Object] object The object associated with the field.
12
+ # @attr_reader [Hash] attributes Attributes for the field.
13
+ # @attr_accessor [Object] value The value of the field.
14
+ class Builder < Structure::Node
15
+ include Phlex::Helpers
16
+ include Options::Associations
17
+ include Options::Attachments
18
+ include Options::Descriptions
19
+ include Options::Hints
20
+ include Options::InferredTypes
21
+ include Options::Labels
22
+ include Options::Placeholders
23
+
24
+ class DOM < Structure::DOM; end
25
+
26
+ class FieldCollection < Structure::FieldCollection; end
27
+
28
+ attr_reader :dom, :options, :object, :value
29
+
30
+ # Initializes a new FieldBuilder instance.
31
+ #
32
+ # @param key [Symbol, String] The key for the field.
33
+ # @param parent [Structure::Namespace] The parent object.
34
+ # @param object [Object, nil] The associated object.
35
+ # @param value [Object] The initial value for the field.
36
+ # @param options [Hash] Additional options for the field.
37
+ def initialize(key, parent:, object: nil, value: NIL_VALUE, **options)
38
+ super(key, parent: parent)
39
+
40
+ @object = object
41
+ @value = determine_initial_value(value)
42
+ @options = options
43
+ @dom = self.class::DOM.new(field: self)
44
+ end
45
+
46
+ # Creates a repeated field collection.
47
+ #
48
+ # @param range [#each] The collection of items to generate displays for.
49
+ # @yield [block] The block to be executed for each item in the collection.
50
+ # @return [FieldCollection] The field collection.
51
+ def repeated(collection = nil, &)
52
+ self.class::FieldCollection.new(field: self, collection:, &)
53
+ end
54
+
55
+ protected
56
+
57
+ def has_value?
58
+ value.present?
59
+ end
60
+
61
+ def determine_initial_value(value)
62
+ return value unless value == NIL_VALUE
63
+
64
+ determine_value_from_object
65
+ end
66
+
67
+ def determine_value_from_object
68
+ object.respond_to?(key) ? object.public_send(key) : nil
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlexi
4
+ module Field
5
+ module Components
6
+ class Base < COMPONENT_BASE
7
+ attr_reader :field, :attributes
8
+
9
+ def initialize(field, **attributes)
10
+ @field = field
11
+ @attributes = attributes
12
+
13
+ build_attributes
14
+ build_component_class
15
+ end
16
+
17
+ protected
18
+
19
+ def build_attributes
20
+ attributes.fetch(:id) { attributes[:id] = "#{field.dom.id}_#{component_name}" }
21
+ end
22
+
23
+ def build_component_class
24
+ return if attributes[:class] == false
25
+
26
+ attributes[:class] = tokens(component_name, attributes[:class])
27
+ end
28
+
29
+ def component_name
30
+ @component_name ||= self.class.name.demodulize.underscore
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlexi
4
+ module Field
5
+ module Options
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
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlexi
4
+ module Field
5
+ module Options
6
+ module Attachments
7
+ protected
8
+
9
+ def attachment_reflection
10
+ @attachment_reflection ||= find_attachment_reflection
11
+ end
12
+
13
+ def find_attachment_reflection
14
+ if object.class.respond_to?(:reflect_on_attachment)
15
+ object.class.reflect_on_attachment(key)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlexi
4
+ module Field
5
+ module Options
6
+ module Descriptions
7
+ def description(description = nil)
8
+ if description.nil?
9
+ options[:description]
10
+ else
11
+ options[:description] = description
12
+ self
13
+ end
14
+ end
15
+
16
+ def has_description?
17
+ description.present?
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlexi
4
+ module Field
5
+ module Options
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
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bigdecimal"
4
+
5
+ module Phlexi
6
+ module Field
7
+ module Options
8
+ module InferredTypes
9
+ def inferred_field_type
10
+ @inferred_field_type ||= infer_field_type
11
+ end
12
+
13
+ def inferred_field_component
14
+ @inferred_component ||= infer_field_component
15
+ end
16
+
17
+ private
18
+
19
+ def infer_field_component
20
+ case inferred_field_type
21
+ when :string, :text
22
+ infer_string_field_component(key)
23
+ when :integer, :float, :decimal
24
+ :number
25
+ when :date, :datetime, :time
26
+ :date
27
+ when :boolean
28
+ :boolean
29
+ when :json, :jsonb, :hstore
30
+ :code
31
+ else
32
+ if association_reflection
33
+ :association
34
+ elsif attachment_reflection
35
+ :attachment
36
+ else
37
+ :text
38
+ end
39
+ end
40
+ end
41
+
42
+ def infer_field_type
43
+ if object.class.respond_to?(:columns_hash)
44
+ # ActiveRecord object
45
+ column = object.class.columns_hash[key.to_s]
46
+ return column.type if column
47
+ end
48
+
49
+ if object.class.respond_to?(:attribute_types)
50
+ # ActiveModel::Attributes
51
+ custom_type = object.class.attribute_types[key.to_s]
52
+ return custom_type.type if custom_type&.type
53
+ end
54
+
55
+ # Check if object responds to the key
56
+ if object.respond_to?(key)
57
+ # Fallback to inferring type from the value
58
+ return infer_field_type_from_value(object.send(key))
59
+ end
60
+
61
+ # Default to string if we can't determine the type
62
+ :string
63
+ end
64
+
65
+ def infer_field_type_from_value(value)
66
+ case value
67
+ when Integer
68
+ :integer
69
+ when Float
70
+ :float
71
+ when BigDecimal
72
+ :decimal
73
+ when TrueClass, FalseClass
74
+ :boolean
75
+ when Date
76
+ :date
77
+ when Time, DateTime
78
+ :datetime
79
+ when Hash
80
+ :json
81
+ else
82
+ :string
83
+ end
84
+ end
85
+
86
+ def infer_string_field_component(key)
87
+ key = key.to_s.downcase
88
+
89
+ return :password if is_password_field?
90
+
91
+ custom_type = custom_string_field_type(key)
92
+ return custom_type if custom_type
93
+
94
+ :text
95
+ end
96
+
97
+ def custom_string_field_type(key)
98
+ custom_mappings = {
99
+ /url$|^link|^site/ => :url,
100
+ /^email/ => :email,
101
+ /^search/ => :search,
102
+ /phone|tel(ephone)?/ => :phone,
103
+ /^time/ => :time,
104
+ /^date/ => :date,
105
+ /^number|_count$|_amount$/ => :number,
106
+ /^color/ => :color
107
+ }
108
+
109
+ custom_mappings.each do |pattern, type|
110
+ return type if key.match?(pattern)
111
+ end
112
+
113
+ nil
114
+ end
115
+
116
+ def is_password_field?
117
+ key = self.key.to_s.downcase
118
+
119
+ exact_matches = ["password"]
120
+ prefixes = ["encrypted_"]
121
+ suffixes = ["_password", "_digest", "_hash", "_token"]
122
+
123
+ exact_matches.include?(key) ||
124
+ prefixes.any? { |prefix| key.start_with?(prefix) } ||
125
+ suffixes.any? { |suffix| key.end_with?(suffix) }
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlexi
4
+ module Field
5
+ module Options
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
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Phlexi
4
+ module Field
5
+ module Options
6
+ module Placeholders
7
+ def placeholder(placeholder = nil)
8
+ if placeholder.nil?
9
+ options[:placeholder]
10
+ else
11
+
12
+ options[:placeholder] = placeholder
13
+ self
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end