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
@@ -1,65 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Form
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 association_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,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Form
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,207 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Form
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 Form
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 association_reflection_validators
18
- association_reflection ? object.class.validators_on(association_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
@@ -1,62 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Form
5
- module Structure
6
- # Generates DOM IDs, names, etc. for a Field, Namespace, or Node based on
7
- # norms that were established by Rails. These can be used outsidef or Rails in
8
- # other Ruby web frameworks since it has no dependencies on Rails.
9
- class DOM
10
- def initialize(field:)
11
- @field = field
12
- end
13
-
14
- # Converts the value of the field to a String, which is required to work
15
- # with Phlex. Assumes that `Object#to_s` emits a format suitable for the web form.
16
- def value
17
- @field.value.to_s
18
- end
19
-
20
- # Walks from the current node to the parent node, grabs the names, and separates
21
- # them with a `_` for a DOM ID.
22
- def id
23
- @id ||= begin
24
- root, *rest = lineage
25
- root_key = root.respond_to?(:dom_id) ? root.dom_id : root.key
26
- rest.map(&:key).unshift(root_key).join("_")
27
- end
28
- end
29
-
30
- # The `name` attribute of a node, which is influenced by Rails.
31
- # All node names, except the parent node, are wrapped in a `[]` and collections
32
- # are left empty. For example, `user[addresses][][street]` would be created for a form with
33
- # data shaped like `{user: {addresses: [{street: "Sesame Street"}]}}`.
34
- def name
35
- @name ||= begin
36
- root, *names = keys
37
- names.map { |name| "[#{name}]" }.unshift(root).join
38
- end
39
- end
40
-
41
- # One-liner way of walking from the current node all the way up to the parent.
42
- def lineage
43
- @lineage ||= Enumerator.produce(@field, &:parent).take_while(&:itself).reverse
44
- end
45
-
46
- # Emit the id, name, and value in an HTML tag-ish that doesnt have an element.
47
- def inspect
48
- "<#{self.class.name} id=#{id.inspect} name=#{name.inspect} value=#{value.inspect}/>"
49
- end
50
-
51
- private
52
-
53
- def keys
54
- @keys ||= lineage.map do |node|
55
- # If the parent of a field is a field, the name should be nil.
56
- node.key unless node.parent.is_a? FieldBuilder
57
- end
58
- end
59
- end
60
- end
61
- end
62
- end
@@ -1,243 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "phlex"
4
-
5
- module Phlexi
6
- module Form
7
- module Structure
8
- # FieldBuilder class is responsible for building form fields with various options and components.
9
- #
10
- # @attr_reader [Structure::DOM] dom The DOM structure for the field.
11
- # @attr_reader [Hash] options Options for the field.
12
- # @attr_reader [Object] object The object associated with the field.
13
- # @attr_reader [Hash] attributes Attributes for the field.
14
- # @attr_accessor [Object] value The value of the field.
15
- class FieldBuilder < Node
16
- include Phlex::Helpers
17
- include FieldOptions::Associations
18
- include FieldOptions::Themes
19
- include FieldOptions::Validators
20
- include FieldOptions::Labels
21
- include FieldOptions::Hints
22
- include FieldOptions::Errors
23
- include FieldOptions::InferredTypes
24
- include FieldOptions::Collection
25
- include FieldOptions::Placeholder
26
- include FieldOptions::Required
27
- include FieldOptions::Autofocus
28
- include FieldOptions::Disabled
29
- include FieldOptions::Readonly
30
- include FieldOptions::Length
31
- include FieldOptions::MinMax
32
- include FieldOptions::Pattern
33
- include FieldOptions::Multiple
34
- include FieldOptions::Limit
35
-
36
- attr_reader :dom, :options, :object, :input_attributes, :value
37
-
38
- # Initializes a new FieldBuilder instance.
39
- #
40
- # @param key [Symbol, String] The key for the field.
41
- # @param parent [Structure::Namespace] The parent object.
42
- # @param object [Object, nil] The associated object.
43
- # @param value [Object] The initial value for the field.
44
- # @param input_attributes [Hash] Default attributes to apply to input fields.
45
- # @param options [Hash] Additional options for the field.
46
- def initialize(key, parent:, object: nil, value: NIL_VALUE, input_attributes: {}, **options)
47
- super(key, parent: parent)
48
-
49
- @object = object
50
- @value = determine_initial_value(value)
51
- @input_attributes = input_attributes
52
- @options = options
53
- @dom = Structure::DOM.new(field: self)
54
- end
55
-
56
- # Creates a label tag for the field.
57
- #
58
- # @param attributes [Hash] Additional attributes for the label.
59
- # @return [Components::Label] The label component.
60
- def label_tag(**, &)
61
- create_component(Components::Label, :label, **, &)
62
- end
63
-
64
- # Creates an input tag for the field.
65
- #
66
- # @param attributes [Hash] Additional attributes for the input.
67
- # @return [Components::Input] The input component.
68
- def input_tag(**, &)
69
- create_component(Components::Input, :input, **, &)
70
- end
71
-
72
- def file_input_tag(**, &)
73
- create_component(Components::FileInput, :file, **, &)
74
- end
75
-
76
- # Creates a checkbox tag for the field.
77
- #
78
- # @param attributes [Hash] Additional attributes for the checkbox.
79
- # @return [Components::Checkbox] The checkbox component.
80
- def checkbox_tag(**, &)
81
- create_component(Components::Checkbox, :checkbox, **, &)
82
- end
83
-
84
- # Creates collection checkboxes for the field.
85
- #
86
- # @param attributes [Hash] Additional attributes for the collection checkboxes.
87
- # @yield [block] The block to be executed for each checkbox.
88
- # @return [Components::CollectionCheckboxes] The collection checkboxes component.
89
- def collection_checkboxes_tag(**, &)
90
- create_component(Components::CollectionCheckboxes, :collection_checkboxes, **, &)
91
- end
92
-
93
- # Creates a radio button tag for the field.
94
- #
95
- # @param attributes [Hash] Additional attributes for the radio button.
96
- # @return [Components::RadioButton] The radio button component.
97
- def radio_button_tag(**, &)
98
- create_component(Components::RadioButton, :radio, **, &)
99
- end
100
-
101
- # Creates collection radio buttons for the field.
102
- #
103
- # @param attributes [Hash] Additional attributes for the collection radio buttons.
104
- # @yield [block] The block to be executed for each radio button.
105
- # @return [Components::CollectionRadioButtons] The collection radio buttons component.
106
- def collection_radio_buttons_tag(**, &)
107
- create_component(Components::CollectionRadioButtons, :collection_radio_buttons, **, &)
108
- end
109
-
110
- # Creates a textarea tag for the field.
111
- #
112
- # @param attributes [Hash] Additional attributes for the textarea.
113
- # @return [Components::Textarea] The textarea component.
114
- def textarea_tag(**, &)
115
- create_component(Components::Textarea, :textarea, **, &)
116
- end
117
-
118
- # Creates a select tag for the field.
119
- #
120
- # @param attributes [Hash] Additional attributes for the select.
121
- # @return [Components::Select] The select component.
122
- def select_tag(**, &)
123
- create_component(Components::Select, :select, **, &)
124
- end
125
-
126
- def input_array_tag(**, &)
127
- create_component(Components::InputArray, :array, **, &)
128
- end
129
-
130
- # Creates a hint tag for the field.
131
- #
132
- # @param attributes [Hash] Additional attributes for the hint.
133
- # @return [Components::Hint] The hint component.
134
- def hint_tag(**, &)
135
- create_component(Components::Hint, :hint, **, &)
136
- end
137
-
138
- # Creates an error tag for the field.
139
- #
140
- # @param attributes [Hash] Additional attributes for the error.
141
- # @return [Components::Error] The error component.
142
- def error_tag(**, &)
143
- create_component(Components::Error, :error, **, &)
144
- end
145
-
146
- # Creates a full error tag for the field.
147
- #
148
- # @param attributes [Hash] Additional attributes for the full error.
149
- # @return [Components::FullError] The full error component.
150
- def full_error_tag(**, &)
151
- create_component(Components::FullError, :full_error, **, &)
152
- end
153
-
154
- # Wraps the field with additional markup.
155
- #
156
- # @param inner [Hash] Attributes for the inner wrapper.
157
- # @param attributes [Hash] Additional attributes for the wrapper.
158
- # @yield [block] The block to be executed within the wrapper.
159
- # @return [Components::Wrapper] The wrapper component.
160
- def wrapped(inner: {}, **attributes, &)
161
- attributes = apply_component_theme(attributes, :wrapper)
162
- inner = apply_component_theme(inner, :inner_wrapper)
163
- Components::Wrapper.new(self, inner: inner, **attributes, &)
164
- end
165
-
166
- # Creates a repeated field collection.
167
- #
168
- # @param range [Integer, #to_a] The range of keys for each field.
169
- # If an integer (e.g. 6) is passed, it is converted to a range = 1..6
170
- # @yield [block] The block to be executed for each item in the collection.
171
- # @return [FieldCollection] The field collection.
172
- def repeated(range = nil, &)
173
- FieldCollection.new(field: self, range: range, &)
174
- end
175
-
176
- # Creates a submit button
177
- #
178
- # @param attributes [Hash] Additional attributes for the submit.
179
- # @return [Components::SubmitButton] The submit button component.
180
- def submit_button_tag(**, &)
181
- create_component(Components::SubmitButton, :submit_button, **, &)
182
- end
183
-
184
- def extract_input(params)
185
- raise "field##{dom.name} did not define an input component" unless @field_input_extractor
186
-
187
- @field_input_extractor.extract_input(params)
188
- end
189
-
190
- protected
191
-
192
- def create_component(component_class, theme_key, **attributes, &)
193
- attributes = mix(input_attributes, attributes) if component_class.include?(Phlexi::Form::Components::Concerns::HandlesInput)
194
- component = component_class.new(self, **apply_component_theme(attributes, theme_key), &)
195
- if component_class.include?(Components::Concerns::ExtractsInput)
196
- raise "input component already defined: #{@field_input_extractor.inspect}" if @field_input_extractor
197
-
198
- @field_input_extractor = component
199
- end
200
-
201
- component
202
- end
203
-
204
- def apply_component_theme(attributes, theme_key)
205
- theme_key = attributes.delete(:theme) || theme_key
206
- if attributes.key?(:class!)
207
- attributes
208
- else
209
- mix({class: themed(theme_key)}, attributes)
210
- end
211
- end
212
-
213
- def has_value?
214
- value.present?
215
- end
216
-
217
- def determine_initial_value(value)
218
- return value unless value == NIL_VALUE
219
-
220
- determine_value_from_association || determine_value_from_object
221
- end
222
-
223
- def determine_value_from_object
224
- object.respond_to?(key) ? object.public_send(key) : nil
225
- end
226
-
227
- def determine_value_from_association
228
- return nil unless association_reflection.present?
229
-
230
- value = object.public_send(key)
231
- case association_reflection.macro
232
- when :has_many, :has_and_belongs_to_many
233
- value&.map { |v| v.public_send(association_reflection.klass.primary_key) }
234
- when :belongs_to, :has_one
235
- value&.public_send(association_reflection.klass.primary_key)
236
- else
237
- raise ArgumentError, "Unsupported association type: #{association_reflection.macro}"
238
- end
239
- end
240
- end
241
- end
242
- end
243
- end
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Phlexi
4
- module Form
5
- module Structure
6
- # Superclass for Namespace and Field classes. Represents a node in the form tree structure.
7
- #
8
- # @attr_reader [Symbol] key The node's key
9
- # @attr_reader [Node, nil] parent The node's parent in the tree structure
10
- class Node
11
- attr_reader :key, :parent
12
-
13
- # Initializes a new Node instance.
14
- #
15
- # @param key [Symbol, String] The key for the node
16
- # @param parent [Node, nil] The parent node
17
- def initialize(key, parent:)
18
- @key = :"#{key}"
19
- @parent = parent
20
- end
21
-
22
- def inspect
23
- "<#{self.class.name} key=#{key.inspect} parent=#{id.inspect} />"
24
- end
25
- end
26
- end
27
- end
28
- end