phlexi-display 0.0.1 → 0.0.3

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/lib/phlexi/display/base.rb +37 -169
  3. data/lib/phlexi/display/builder.rb +117 -0
  4. data/lib/phlexi/display/components/base.rb +1 -42
  5. data/lib/phlexi/display/components/concerns/displays_value.rb +54 -0
  6. data/lib/phlexi/display/components/date_time.rb +49 -0
  7. data/lib/phlexi/display/components/{error.rb → description.rb} +5 -5
  8. data/lib/phlexi/display/components/hint.rb +1 -1
  9. data/lib/phlexi/display/components/label.rb +3 -15
  10. data/lib/phlexi/display/components/number.rb +37 -0
  11. data/lib/phlexi/display/components/placeholder.rb +15 -0
  12. data/lib/phlexi/display/components/string.rb +17 -0
  13. data/lib/phlexi/display/components/wrapper.rb +4 -18
  14. data/lib/phlexi/display/theme.rb +22 -0
  15. data/lib/phlexi/display/version.rb +1 -1
  16. data/lib/phlexi/display.rb +1 -1
  17. metadata +24 -43
  18. data/lib/phlexi/display/components/checkbox.rb +0 -48
  19. data/lib/phlexi/display/components/collection_checkboxes.rb +0 -44
  20. data/lib/phlexi/display/components/collection_radio_buttons.rb +0 -35
  21. data/lib/phlexi/display/components/concerns/handles_array_input.rb +0 -21
  22. data/lib/phlexi/display/components/concerns/handles_input.rb +0 -53
  23. data/lib/phlexi/display/components/concerns/has_options.rb +0 -37
  24. data/lib/phlexi/display/components/concerns/submits_form.rb +0 -39
  25. data/lib/phlexi/display/components/file_input.rb +0 -32
  26. data/lib/phlexi/display/components/full_error.rb +0 -21
  27. data/lib/phlexi/display/components/input.rb +0 -84
  28. data/lib/phlexi/display/components/input_array.rb +0 -45
  29. data/lib/phlexi/display/components/radio_button.rb +0 -41
  30. data/lib/phlexi/display/components/select.rb +0 -69
  31. data/lib/phlexi/display/components/submit_button.rb +0 -41
  32. data/lib/phlexi/display/components/textarea.rb +0 -34
  33. data/lib/phlexi/display/field_options/associations.rb +0 -21
  34. data/lib/phlexi/display/field_options/autofocus.rb +0 -18
  35. data/lib/phlexi/display/field_options/collection.rb +0 -54
  36. data/lib/phlexi/display/field_options/disabled.rb +0 -18
  37. data/lib/phlexi/display/field_options/errors.rb +0 -92
  38. data/lib/phlexi/display/field_options/hints.rb +0 -22
  39. data/lib/phlexi/display/field_options/inferred_types.rb +0 -155
  40. data/lib/phlexi/display/field_options/labels.rb +0 -28
  41. data/lib/phlexi/display/field_options/length.rb +0 -53
  42. data/lib/phlexi/display/field_options/limit.rb +0 -66
  43. data/lib/phlexi/display/field_options/min_max.rb +0 -92
  44. data/lib/phlexi/display/field_options/multiple.rb +0 -65
  45. data/lib/phlexi/display/field_options/pattern.rb +0 -38
  46. data/lib/phlexi/display/field_options/placeholder.rb +0 -18
  47. data/lib/phlexi/display/field_options/readonly.rb +0 -18
  48. data/lib/phlexi/display/field_options/required.rb +0 -37
  49. data/lib/phlexi/display/field_options/themes.rb +0 -207
  50. data/lib/phlexi/display/field_options/validators.rb +0 -48
  51. data/lib/phlexi/display/option_mapper.rb +0 -154
  52. data/lib/phlexi/display/structure/dom.rb +0 -62
  53. data/lib/phlexi/display/structure/field_builder.rb +0 -236
  54. data/lib/phlexi/display/structure/field_collection.rb +0 -54
  55. data/lib/phlexi/display/structure/namespace.rb +0 -135
  56. data/lib/phlexi/display/structure/namespace_collection.rb +0 -48
  57. data/lib/phlexi/display/structure/node.rb +0 -18
@@ -1,66 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Display
5
- module FieldOptions
6
- module Limit
7
- def limit(limit = nil)
8
- if limit.nil?
9
- options[:limit] = options.fetch(:limit) { calculate_limit }
10
- else
11
- options[:limit] = limit
12
- self
13
- end
14
- end
15
-
16
- private
17
-
18
- def calculate_limit
19
- return unless multiple?
20
-
21
- limit_from_validators = [
22
- limit_from_length_validator,
23
- limit_from_inclusion_validator
24
- ].compact.min
25
-
26
- limit_from_validators || limit_from_db_column
27
- end
28
-
29
- def limit_from_length_validator
30
- length_validator = find_validator(:length)
31
- return unless length_validator
32
-
33
- length_validator.options[:maximum]
34
- end
35
-
36
- def limit_from_inclusion_validator
37
- return unless has_validators?
38
-
39
- inclusion_validator = find_validator(:inclusion)
40
- return unless inclusion_validator
41
-
42
- in_option = inclusion_validator.options[:in]
43
- in_option.is_a?(Array) ? in_option.size : nil
44
- end
45
-
46
- def limit_from_db_column
47
- return unless object.class.respond_to?(:columns_hash)
48
-
49
- column = object.class.columns_hash[key.to_s]
50
- return unless column
51
-
52
- case object.class.connection.adapter_name.downcase
53
- when "postgresql"
54
- if column.array?
55
- # Check if there's a limit on the array size
56
- column.limit
57
- elsif column.type == :string && column.sql_type.include?("[]")
58
- # For string arrays, extract the limit if specified
59
- column.sql_type.match(/\[(\d+)\]/)&.captures&.first&.to_i
60
- end
61
- end
62
- end
63
- end
64
- end
65
- end
66
- end
@@ -1,92 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Display
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
@@ -1,65 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Display
5
- module FieldOptions
6
- module Multiple
7
- def multiple?
8
- options[:multiple] = options.fetch(:multiple) { calculate_multiple_field_value }
9
- end
10
-
11
- def multiple!(multiple = true)
12
- options[:multiple] = multiple
13
- self
14
- end
15
-
16
- private
17
-
18
- def calculate_multiple_field_value
19
- return true if reflection&.macro == :has_many
20
- return true if multiple_field_array_attribute?
21
-
22
- check_multiple_field_from_validators
23
- end
24
-
25
- def multiple_field_array_attribute?
26
- return false unless object.class.respond_to?(:columns_hash)
27
-
28
- column = object.class.columns_hash[key.to_s]
29
- return false unless column
30
-
31
- case object.class.connection.adapter_name.downcase
32
- when "postgresql"
33
- column.array? || (column.type == :string && column.sql_type.include?("[]"))
34
- end # || object.class.attribute_types[key.to_s].is_a?(ActiveRecord::Type::Serialized)
35
- rescue
36
- # Rails.logger.warn("Error checking multiple field array attribute: #{e.message}")
37
- false
38
- end
39
-
40
- def check_multiple_field_from_validators
41
- inclusion_validator = find_validator(:inclusion)
42
- length_validator = find_validator(:length)
43
-
44
- return false unless inclusion_validator || length_validator
45
-
46
- check_multiple_field_inclusion_validator(inclusion_validator) ||
47
- check_multiple_field_length_validator(length_validator)
48
- end
49
-
50
- def check_multiple_field_inclusion_validator(validator)
51
- return false unless validator
52
- in_option = validator.options[:in]
53
- return false unless in_option.is_a?(Array)
54
-
55
- validator.options[:multiple] == true || (multiple_field_array_attribute? && in_option.size > 1)
56
- end
57
-
58
- def check_multiple_field_length_validator(validator)
59
- return false unless validator
60
- validator.options[:maximum].to_i > 1 if validator.options[:maximum]
61
- end
62
- end
63
- end
64
- end
65
- end
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Display
5
- module FieldOptions
6
- module Pattern
7
- def pattern(pattern = nil)
8
- if pattern.nil?
9
- options[:pattern] = options.fetch(:pattern) { calculate_pattern }
10
- else
11
- options[:pattern] = pattern
12
- self
13
- end
14
- end
15
-
16
- private
17
-
18
- def calculate_pattern
19
- if (pattern_validator = find_pattern_validator) && (with = pattern_validator.options[:with])
20
- evaluate_format_validator_option(with).source
21
- end
22
- end
23
-
24
- def find_pattern_validator
25
- find_validator(:format)
26
- end
27
-
28
- def evaluate_format_validator_option(option)
29
- if option.respond_to?(:call)
30
- option.call(object)
31
- else
32
- option
33
- end
34
- end
35
- end
36
- end
37
- end
38
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Display
5
- module FieldOptions
6
- module Placeholder
7
- def placeholder(placeholder = nil)
8
- if placeholder.nil?
9
- options[:placeholder]
10
- else
11
- options[:placeholder] = placeholder
12
- self
13
- end
14
- end
15
- end
16
- end
17
- end
18
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Display
5
- module FieldOptions
6
- module Readonly
7
- def readonly?
8
- options[:readonly] == true
9
- end
10
-
11
- def readonly!(readonly = true)
12
- options[:readonly] = readonly
13
- self
14
- end
15
- end
16
- end
17
- end
18
- end
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Display
5
- module FieldOptions
6
- module Required
7
- def required?
8
- options[:required] = options.fetch(:required) { calculate_required }
9
- end
10
-
11
- def required!(required = true)
12
- options[:required] = required
13
- self
14
- end
15
-
16
- private
17
-
18
- def calculate_required
19
- if has_validators?
20
- required_by_validators?
21
- else
22
- required_by_default?
23
- end
24
- end
25
-
26
- def required_by_validators?
27
- (attribute_validators + reflection_validators).any? { |v| v.kind == :presence && valid_validator?(v) }
28
- end
29
-
30
- def required_by_default?
31
- # TODO: get this from configuration
32
- false
33
- end
34
- end
35
- end
36
- end
37
- end
@@ -1,207 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Display
5
- module FieldOptions
6
- module Themes
7
- # Resolves theme classes for components based on their type and the validity state of the field.
8
- #
9
- # This method is responsible for determining the appropriate CSS classes for a given form component.
10
- # It considers both the base theme for the component type and any additional theming based on the
11
- # component's validity state (valid, invalid, or neutral). The method supports a hierarchical
12
- # theming system, allowing for cascading themes and easy customization.
13
- #
14
- # @param component [Symbol, String] The type of form component (e.g., :input, :label, :wrapper)
15
- #
16
- # @return [String, nil] A string of CSS classes for the component, or nil if no theme is applied
17
- #
18
- # @example Basic usage
19
- # themed(:input)
20
- # # => "w-full p-2 border rounded-md shadow-sm font-medium text-sm dark:bg-gray-700"
21
- #
22
- # @example Usage with validity state
23
- # # Assuming the field has errors
24
- # themed(:input)
25
- # # => "w-full p-2 border rounded-md shadow-sm font-medium text-sm dark:bg-gray-700 bg-red-50 border-red-500 text-red-900"
26
- #
27
- # @example Cascading themes
28
- # # Assuming textarea inherits from input in the theme definition
29
- # themed(:textarea)
30
- # # => "w-full p-2 border rounded-md shadow-sm font-medium text-sm dark:bg-gray-700"
31
- #
32
- # @note The actual CSS classes returned will depend on the theme definitions in the `theme` hash
33
- # and any overrides specified in the `options` hash.
34
- #
35
- # @see #resolve_theme
36
- # @see #resolve_validity_theme
37
- # @see #theme
38
- def themed(component)
39
- return unless component
40
-
41
- tokens(resolve_theme(component), resolve_validity_theme(component)).presence
42
- end
43
-
44
- protected
45
-
46
- # Recursively resolves the theme for a given property, handling nested symbol references
47
- #
48
- # @param property [Symbol, String] The theme property to resolve
49
- # @param visited [Set] Set of already visited properties to prevent infinite recursion
50
- # @return [String, nil] The resolved theme value or nil if not found
51
- #
52
- # @example Resolving a nested theme
53
- # # Assuming the theme is: { input: :base_input, base_input: "some-class" }
54
- # resolve_theme(:input)
55
- # # => "some-class"
56
- def resolve_theme(property, visited = Set.new)
57
- return nil if !property.present? || visited.include?(property)
58
- visited.add(property)
59
-
60
- result = theme[property]
61
- if result.is_a?(Symbol)
62
- resolve_theme(result, visited)
63
- else
64
- result
65
- end
66
- end
67
-
68
- # Resolves the theme for a component based on its current validity state
69
- #
70
- # This method determines the validity state of the field (valid, invalid, or neutral)
71
- # and returns the corresponding theme by prepending the state to the component name.
72
- #
73
- # @param property [Symbol, String] The base theme property to resolve
74
- # @return [String, nil] The resolved validity-specific theme or nil if not found
75
- #
76
- # @example Resolving a validity theme
77
- # # Assuming the field has errors and the theme includes { invalid_input: "error-class" }
78
- # resolve_validity_theme(:input)
79
- # # => "error-class"
80
- def resolve_validity_theme(property)
81
- validity_property = if has_errors?
82
- :"invalid_#{property}"
83
- elsif object_valid?
84
- :"valid_#{property}"
85
- else
86
- :"neutral_#{property}"
87
- end
88
-
89
- resolve_theme(validity_property)
90
- end
91
-
92
- # Retrieves or initializes the theme hash for the form builder.
93
- #
94
- # This method returns a hash containing theme definitions for various form components.
95
- # If a theme has been explicitly set in the options, it returns that. Otherwise, it
96
- # initializes and returns a default theme.
97
- #
98
- # The theme hash defines CSS classes or references to other theme keys for different
99
- # components and their states (e.g., valid, invalid, neutral).
100
- #
101
- # @return [Hash] A hash containing theme definitions for form components
102
- #
103
- # @example Accessing the theme
104
- # theme[:input]
105
- # # => "w-full p-2 border rounded-md shadow-sm font-medium text-sm dark:bg-gray-700"
106
- #
107
- # @example Accessing a validity-specific theme
108
- # theme[:invalid_input]
109
- # # => "bg-red-50 border-red-500 text-red-900 placeholder-red-700 focus:ring-red-500 focus:border-red-500"
110
- #
111
- # @example Theme inheritance
112
- # theme[:textarea] # Returns :input, indicating textarea inherits input's theme
113
- # theme[:valid_textarea] # Returns :valid_input
114
- #
115
- # @note The actual content of the theme hash depends on the default_theme method
116
- # and any theme overrides specified in the options when initializing the field builder.
117
- #
118
- # @see #default_theme
119
- def theme
120
- @theme ||= options[:theme] || default_theme
121
- end
122
-
123
- # Defines and returns the default theme hash for the field builder.
124
- #
125
- # This method returns a hash containing the base theme definitions for various components.
126
- # It sets up the default styling and relationships between different components and their states.
127
- # The theme uses a combination of explicit CSS classes and symbolic references to other theme keys,
128
- # allowing for a flexible and inheritance-based theming system.
129
- #
130
- # @return [Hash] A frozen hash containing default theme definitions for components
131
- #
132
- # @example Accessing the default theme
133
- # default_theme[:input]
134
- # # => nil (indicates that :input doesn't have a default and should be defined by the user)
135
- #
136
- # @example Theme inheritance
137
- # default_theme[:textarea]
138
- # # => :input (indicates that :textarea inherits from :input)
139
- #
140
- # @example Validity state theming
141
- # default_theme[:valid_textarea]
142
- # # => :valid_input (indicates that :valid_textarea inherits from :valid_input)
143
- #
144
- # @note This method returns a frozen hash to prevent accidental modifications.
145
- # To customize the theme, users should provide their own theme hash when initializing the field builder.
146
- # @note Most theme values are set to nil or commented out in the default theme to encourage users
147
- # to define their own styles while maintaining the relationships between components and states.
148
- #
149
- # @see #theme
150
- def default_theme
151
- {
152
- # # input
153
- # input: nil,
154
- # valid_input: nil,
155
- # invalid_input: nil,
156
- # neutral_input: nil,
157
-
158
- # textarea
159
- textarea: :input,
160
- valid_textarea: :valid_input,
161
- invalid_textarea: :invalid_input,
162
- neutral_textarea: :neutral_input,
163
-
164
- # select
165
- select: :input,
166
- valid_select: :valid_input,
167
- invalid_select: :invalid_input,
168
- neutral_select: :neutral_input,
169
-
170
- # file
171
- file: :input,
172
- valid_file: :valid_input,
173
- invalid_file: :invalid_input,
174
- neutral_file: :neutral_input,
175
-
176
- # misc
177
- # label: nil,
178
- # hint: nil,
179
- # error: nil,
180
- full_error: :error,
181
- # wrapper: nil,
182
- # inner_wrapper: nil,
183
- submit_button: :button
184
-
185
- # # label themes
186
- # label: "md:w-1/6 mt-2 block mb-2 text-sm font-medium",
187
- # invalid_label: "text-red-700 dark:text-red-500",
188
- # valid_label: "text-green-700 dark:text-green-500",
189
- # neutral_label: "text-gray-700 dark:text-white",
190
- # # input themes
191
- # input: "w-full p-2 border rounded-md shadow-sm font-medium text-sm dark:bg-gray-700",
192
- # invalid_input: "bg-red-50 border-red-500 dark:border-red-500 text-red-900 dark:text-red-500 placeholder-red-700 dark:placeholder-red-500 focus:ring-red-500 focus:border-red-500",
193
- # valid_input: "bg-green-50 border-green-500 dark:border-green-500 text-green-900 dark:text-green-400 placeholder-green-700 dark:placeholder-green-500 focus:ring-green-500 focus:border-green-500",
194
- # neutral_input: "border-gray-300 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white focus:ring-primary-500 focus:border-primary-500",
195
- # # hint themes
196
- # hint: "mt-2 text-sm text-gray-500 dark:text-gray-200",
197
- # # error themes
198
- # error: "mt-2 text-sm text-red-600 dark:text-red-500",
199
- # # wrapper themes
200
- # wrapper: "flex flex-col md:flex-row items-start space-y-2 md:space-y-0 md:space-x-2 mb-4",
201
- # inner_wrapper: "md:w-5/6 w-full",
202
- }.freeze
203
- end
204
- end
205
- end
206
- end
207
- end
@@ -1,48 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Display
5
- module FieldOptions
6
- module Validators
7
- private
8
-
9
- def has_validators?
10
- @has_validators ||= object.class.respond_to?(:validators_on)
11
- end
12
-
13
- def attribute_validators
14
- object.class.validators_on(key)
15
- end
16
-
17
- def reflection_validators
18
- reflection ? object.class.validators_on(reflection.name) : []
19
- end
20
-
21
- def valid_validator?(validator)
22
- !conditional_validators?(validator) && action_validator_match?(validator)
23
- end
24
-
25
- def conditional_validators?(validator)
26
- validator.options.include?(:if) || validator.options.include?(:unless)
27
- end
28
-
29
- def action_validator_match?(validator)
30
- return true unless validator.options.include?(:on)
31
-
32
- case validator.options[:on]
33
- when :save
34
- true
35
- when :create
36
- !object.persisted?
37
- when :update
38
- object.persisted?
39
- end
40
- end
41
-
42
- def find_validator(kind)
43
- attribute_validators.find { |v| v.kind == kind && valid_validator?(v) } if has_validators?
44
- end
45
- end
46
- end
47
- end
48
- end