phlex-forms 0.2.9 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6247172989a97967a471140547a44b29058f77827ce8d699b2f8ae9d0aa60ce
4
- data.tar.gz: 5aaeccec4ad938988ee09ad31f8b42ca92bc29f55fd3a5c481a1d1b21f17d1b8
3
+ metadata.gz: 9541561e98ecd2afda34daf77d2eeebd725aac0badde75fdbf51401628d081f6
4
+ data.tar.gz: 5d4008e8e6e5748ae12fdbcd12a667d016ab30549da71498dbc2e286fe202d81
5
5
  SHA512:
6
- metadata.gz: 4dd96934af47c850597cd9f750bf03f97052301e6d9850239cb864f13fe914d17767b9c00f4b30fb23dcb0ea44c92b4e3d9f463d90e3832823d00dffd6700f70
7
- data.tar.gz: 0a8acefb7a3bb47d002633482592aec0aa425b4b5df99d8a360efeb37455e1b9ea4ff9ba8dd918c4b15cdf08aa72ac8ab8bcbda6f37ef5c8d9f6091e966a540e
6
+ metadata.gz: f10fd9155f6ff9841bdae9c34fb2ac89dbbdbc49df33c012af7efad526fa914d21c6379430e1ee66a1aadeff387dcc9bab353d33cb017ce8b07798dd8058dd2c
7
+ data.tar.gz: 7ea1bdaa7fea101bde79b7975759c79b30bc502fb50c2b18e279a5cf4032b294745055e23f2f66ed22cba33c4e43c637c6499ea3c62059f425a3b3aec141ad83
data/CHANGELOG.md CHANGED
@@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Fixed
11
+
12
+ - **`rich_textarea` forwards its block into `<lexxy-editor>`**: Lexxy configures
13
+ an editor through CHILD elements — `<lexxy-prompt>` for @mentions,
14
+ `<lexxy-code-language-picker>` for code blocks — but the component rendered
15
+ the element with no block and `Form#rich_textarea` never forwarded one, so
16
+ `f.rich_textarea(:content) { ... }` silently dropped its content. A host
17
+ could wire @mentions and get an editor that never prompts, with no error
18
+ anywhere (a host app's mention endpoint 500'd for months unnoticed, because
19
+ nothing ever called it).
20
+ - **`rich_textarea` wires Active Storage's direct-upload endpoints**: Lexxy
21
+ uploads attachments through `data-direct-upload-url` /
22
+ `data-blob-url-template` on the element. Lexxy's own Rails tag helper sets
23
+ them; this component built the element itself and did not, so rich text
24
+ editors silently lost image uploads. Set as PATHS (a multi-host app must not
25
+ pin uploads to whichever host rendered the page first), fully guarded — no
26
+ Rails dependency is introduced, and a caller-supplied value always wins.
27
+
28
+ - **`FileInput` multiple uploads submit an array** (Rails `file_field` parity):
29
+ with `multiple:` set, the input name now gets `[]` appended (unless already
30
+ present), for the standalone component, the `Plain` variant, and the form
31
+ builder path. Without it the browser collapsed a multi-file selection into
32
+ ONE scalar param, which a host app's `params.expect(attr: [])` silently
33
+ discarded — uploads no-opped with a success response.
34
+
10
35
  ### Added
11
36
 
12
37
  - **`checkbox_group` — batched checkbox group for array-valued fields** (the
data/README.md CHANGED
@@ -4,7 +4,7 @@ A model-bound form builder for [Phlex](https://www.phlex.fun) — forms as
4
4
  first-class Phlex classes, types inferred from your model, DaisyUI styling by
5
5
  default (and a plain-HTML theme when you want none), and optional
6
6
  **server-truth live validation** over
7
- [phlex-reactive](https://github.com/mhenrixon/phlex-reactive).
7
+ [phlex-reactive](https://github.com/zoolutions/phlex-reactive).
8
8
 
9
9
  ```ruby
10
10
  class UserForm < Forms::Base
@@ -158,7 +158,7 @@ Both work on inline forms (`f.row { … }`) and inside `fields_for` builders.
158
158
 
159
159
  `as: :tags` renders a polished tag/chip input — label + error/hint chrome and
160
160
  daisyUI styling on top of
161
- [phlex-reactive](https://github.com/mhenrixon/phlex-reactive)'s client-only tag
161
+ [phlex-reactive](https://github.com/zoolutions/phlex-reactive)'s client-only tag
162
162
  primitives (form state, no server round trips). **Requires phlex-reactive** (the `:tags` role is
163
163
  registered only when it's loaded).
164
164
 
data/lib/forms/field.rb CHANGED
@@ -30,6 +30,12 @@ module Forms
30
30
  # PhlexForms::Theme), so the same field renders daisy or plain.
31
31
 
32
32
  def input(*modifiers, type: :text, **)
33
+ # type: :hidden reroutes to #hidden so EVERY path to a hidden field lands on
34
+ # the bare leaf — `f.Input(:token, :hidden)` and `f.Input(:token, type:
35
+ # :hidden)` would otherwise still emit the styled `input w-full`. Positional
36
+ # modifiers are dropped with it: a hidden field has no visual variants.
37
+ return hidden(**) if type.to_s == "hidden"
38
+
33
39
  theme[:input].new(*modifiers, type:, **field_attributes, **)
34
40
  end
35
41
 
@@ -43,8 +49,13 @@ module Forms
43
49
  end
44
50
  alias rich_text_area rich_textarea
45
51
 
52
+ # Bare <input type="hidden">, never the styled :input leaf — daisyui's
53
+ # `.input` beats WebKit's non-!important `input[type=hidden] { display: none }`
54
+ # and turns the field into a focusable phantom tab stop in Safari. Built from
55
+ # the explicit accessors (not field_attributes) so the meaningless
56
+ # `error: invalid?` is never introduced.
46
57
  def hidden(**)
47
- theme[:input].new(type: :hidden, **field_attributes, **)
58
+ theme[:hidden].new(name: field_name, id: field_id, value: field_value, **)
48
59
  end
49
60
 
50
61
  # daisyui v5 "icon/text inside the field" wrapper. The block renders the
@@ -8,7 +8,10 @@ module Forms
8
8
  def initialize(*modifiers, name: nil, id: nil, multiple: false, accept: nil,
9
9
  error: false, disabled: false, required: false, full_width: true, **attributes)
10
10
  @modifiers = normalize_modifiers(modifiers)
11
- @name = name
11
+ # Rails file_field parity: a multiple input needs an array param name, or
12
+ # the browser collapses the selection into one scalar file — which a host
13
+ # app's `params.expect(attr: [])` then silently discards.
14
+ @name = multiple && name && !name.to_s.end_with?("[]") ? "#{name}[]" : name
12
15
  @id = id
13
16
  @multiple = multiple
14
17
  @accept = accept
data/lib/forms/form.rb CHANGED
@@ -78,8 +78,8 @@ module Forms
78
78
  render theme[:submit].new(*, model: @model, **, &)
79
79
  end
80
80
 
81
- def rich_textarea(name, *modifiers, **)
82
- render field_object(name).rich_textarea(*modifiers, **)
81
+ def rich_textarea(name, *modifiers, **, &)
82
+ render field_object(name).rich_textarea(*modifiers, **), &
83
83
  end
84
84
  alias rich_text_area rich_textarea
85
85
 
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Forms
4
+ # A model-bound hidden field: a bare <input type="hidden"> carrying name/id/
5
+ # value plus whatever the caller passes through. Deliberately NOT a
6
+ # DelegatedField/DaisyUI::Input like Forms::Input — a hidden field must never
7
+ # carry visual styling. daisyui's `.input { display: inline-flex }` overrides
8
+ # WebKit's *non*-!important UA rule `input[type=hidden] { display: none }`, so a
9
+ # styled hidden field renders as an empty box and joins the keyboard tab order
10
+ # in Safari (Chromium's UA rule is !important, hiding the bug there).
11
+ #
12
+ # Forms::Hidden.new(name: "user[token]", id: "user_token", value: "abc123")
13
+ #
14
+ # Same component under both themes (:hidden role) — there are no styling
15
+ # classes for a Plain twin to strip. It takes no positional variants and no
16
+ # error:: a hidden field has no visual or error state.
17
+ class Hidden < Phlex::HTML
18
+ def initialize(name: nil, id: nil, value: nil, **attributes)
19
+ @name = name
20
+ @id = id
21
+ @value = value
22
+ @attributes = attributes
23
+ super()
24
+ end
25
+
26
+ def view_template
27
+ input(type: "hidden", name: @name, id: @id, value: @value.to_s, **@attributes)
28
+ end
29
+ end
30
+ end
@@ -14,21 +14,54 @@ module Forms
14
14
  @id = id || name.to_s.gsub(/[\[\]]/, "_").gsub(/_+$/, "")
15
15
  @value = value
16
16
  @options = options
17
+ apply_direct_upload_defaults
17
18
  super()
18
19
  end
19
20
 
20
- def view_template
21
+ # Lexxy configures the editor through CHILD elements — `<lexxy-prompt>` for
22
+ # @mentions, `<lexxy-code-language-picker>` for code blocks — so the block
23
+ # has to reach the element, not be dropped.
24
+ def view_template(&)
21
25
  lexxy_editor(
22
26
  name: @name,
23
27
  id: @id,
24
28
  value: formatted_value,
25
29
  class: editor_classes,
26
- **@options.except(:class, :error)
30
+ **@options.except(:class, :error),
31
+ &
27
32
  )
28
33
  end
29
34
 
30
35
  private
31
36
 
37
+ # Lexxy uploads attachments through Active Storage's direct-upload
38
+ # endpoints, read off the element. Lexxy's own Rails tag helper sets them;
39
+ # this component builds the element itself, so without these an editor
40
+ # silently loses image uploads. Paths, not URLs: a multi-host app must not
41
+ # pin uploads to whichever host rendered the page first.
42
+ #
43
+ # Fully guarded — the gem has no Rails dependency, and a host without
44
+ # Active Storage (or with its own values) is left alone.
45
+ def apply_direct_upload_defaults
46
+ helpers = rails_url_helpers or return
47
+
48
+ data = (@options[:data] ||= {})
49
+ data[:direct_upload_url] ||= helpers.rails_direct_uploads_path
50
+ data[:blob_url_template] ||= helpers.rails_service_blob_path(":signed_id", ":filename")
51
+ rescue StandardError
52
+ nil
53
+ end
54
+
55
+ def rails_url_helpers
56
+ return unless defined?(Rails) && Rails.respond_to?(:application)
57
+
58
+ helpers = Rails.application&.routes&.url_helpers
59
+ return unless helpers.respond_to?(:rails_direct_uploads_path)
60
+ return unless helpers.respond_to?(:rails_service_blob_path)
61
+
62
+ helpers
63
+ end
64
+
32
65
  def formatted_value
33
66
  return nil if @value.nil? || (@value.respond_to?(:blank?) && @value.blank?)
34
67
 
@@ -4,7 +4,9 @@ module PhlexForms
4
4
  # A theme maps component roles (:input, :select, :control, ...) to component
5
5
  # classes. The binding contract — the leaf initializer signatures
6
6
  # (*modifiers, name:, id:, value:, error:, required:, ...) — is the stable
7
- # interface, so the same form class renders under any theme.
7
+ # interface, so the same form class renders under any theme. The one exception
8
+ # is :hidden, which takes name:/id:/value: only — a hidden field has neither a
9
+ # visual variant nor an error state.
8
10
  #
9
11
  # Built-ins:
10
12
  # Theme.daisy — the DaisyUI-delegating Forms::* leaves (default when the
@@ -56,7 +58,7 @@ module PhlexForms
56
58
  textarea: Forms::Textarea, rich_textarea: Forms::RichTextarea,
57
59
  checkbox: Forms::Checkbox, toggle: Forms::Toggle, radio: Forms::Radio,
58
60
  checkbox_group: Forms::CheckboxGroup,
59
- file: Forms::FileInput, wrapped_input: Forms::WrappedInput,
61
+ file: Forms::FileInput, wrapped_input: Forms::WrappedInput, hidden: Forms::Hidden,
60
62
  control: Forms::FormControl, label: Forms::Label,
61
63
  field_error: Forms::FieldError, field_hint: Forms::FieldHint,
62
64
  submit: Forms::Submit, row: Forms::Row, group: Forms::Group,
@@ -74,7 +76,7 @@ module PhlexForms
74
76
  textarea: Forms::Plain::Textarea, rich_textarea: Forms::Plain::Textarea,
75
77
  checkbox: Forms::Plain::Checkbox, toggle: Forms::Plain::Checkbox, radio: Forms::Plain::Radio,
76
78
  checkbox_group: Forms::Plain::CheckboxGroup,
77
- file: Forms::Plain::FileInput, wrapped_input: Forms::Plain::WrappedInput,
79
+ file: Forms::Plain::FileInput, wrapped_input: Forms::Plain::WrappedInput, hidden: Forms::Hidden,
78
80
  control: Forms::Plain::Control, label: Forms::Plain::Label,
79
81
  field_error: Forms::Plain::FieldError, field_hint: Forms::Plain::FieldHint,
80
82
  submit: Forms::Plain::Submit, row: Forms::Plain::Row, group: Forms::Plain::Group,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PhlexForms
4
- VERSION = "0.2.9"
4
+ VERSION = "0.3.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phlex-forms
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.9
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikael Henriksson
@@ -128,6 +128,7 @@ files:
128
128
  - lib/forms/form.rb
129
129
  - lib/forms/form_control.rb
130
130
  - lib/forms/group.rb
131
+ - lib/forms/hidden.rb
131
132
  - lib/forms/input.rb
132
133
  - lib/forms/label.rb
133
134
  - lib/forms/live.rb
@@ -178,13 +179,13 @@ files:
178
179
  - lib/phlex_forms/version.rb
179
180
  - lib/rubocop/phlex_forms/cop/legacy_form_method.rb
180
181
  - lib/rubocop/phlex_forms/cop/raw_form.rb
181
- homepage: https://github.com/mhenrixon/phlex-forms
182
+ homepage: https://github.com/zoolutions/phlex-forms
182
183
  licenses:
183
184
  - MIT
184
185
  metadata:
185
- source_code_uri: https://github.com/mhenrixon/phlex-forms
186
- changelog_uri: https://github.com/mhenrixon/phlex-forms/blob/main/CHANGELOG.md
187
- bug_tracker_uri: https://github.com/mhenrixon/phlex-forms/issues
186
+ source_code_uri: https://github.com/zoolutions/phlex-forms
187
+ changelog_uri: https://github.com/zoolutions/phlex-forms/blob/main/CHANGELOG.md
188
+ bug_tracker_uri: https://github.com/zoolutions/phlex-forms/issues
188
189
  rubygems_mfa_required: 'true'
189
190
  rdoc_options: []
190
191
  require_paths: