phlexi-field 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.
@@ -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