phlex-reactive 0.13.2 → 0.13.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.
@@ -389,6 +389,13 @@ module Phlex
389
389
  # inputs: { title: :string, qty: :number },
390
390
  # outputs: %i[title_preview char_count]
391
391
  #
392
+ # A CHECKBOX contributes its checked state, never its constant .value
393
+ # (issue #262): 1/0 as a :number, "true"/"false" as a :string, and a
394
+ # real boolean as a :boolean. It wins over the hidden companion Rails
395
+ # renders before it. A radio group contributes its checked radio's value.
396
+ #
397
+ # reactive_compute :total, inputs: [:price, { gift_wrap: :boolean }], outputs: %i[total]
398
+ #
392
399
  # An output with no matching form field writes to its reactive_text(:name)
393
400
  # node (textContent); a declared input also mirrors into its own text node
394
401
  # with no reducer. The array form's wire stays byte-identical.
@@ -449,7 +449,7 @@ module Phlex
449
449
  # the baseline is the DOM's own `defaultValue`/`defaultChecked`/
450
450
  # `defaultSelected` from the last server render (dirty = current ≠ default).
451
451
  # The descriptor deep-merges via mix, so a caller's own data-action is
452
- # token-joined, not clobbered (CLAUDE.md Never-Do #8).
452
+ # token-joined, not clobbered (AGENTS.md Never-Do #8).
453
453
  def reactive_field(param, **attrs)
454
454
  # Issue #184: the removed dirty: kwarg lands in **attrs — catch it and
455
455
  # print the reactive_dirty rewrite.
@@ -1232,8 +1232,9 @@ module Phlex
1232
1232
  # The inputs param wire (issue #104). Untyped (array form) → a JSON ARRAY of
1233
1233
  # names, byte-identical to the shipped wire so the client keeps its numeric
1234
1234
  # coercion. Typed (hash form) → a JSON OBJECT of name→type
1235
- # ({"title":"string","qty":"number"}) so the client reads a :string raw and
1236
- # coerces a :number through Number.
1235
+ # ({"title":"string","qty":"number"}) so the client reads a :string raw,
1236
+ # coerces a :number through Number and reads a :boolean as a checkbox's
1237
+ # checked state (issue #262).
1237
1238
  def compute_inputs_param(definition)
1238
1239
  types = definition.input_types
1239
1240
  return definition.inputs.map(&:to_s).to_json if types.nil?
@@ -102,7 +102,8 @@ module Phlex
102
102
  # `input_types` (issue #104) is nil for the ARRAY input form (untyped ⇒ the
103
103
  # client coerces every input through Number, the shipped behavior) and a
104
104
  # { name => type } hash for the typed HASH form (:string reads the field
105
- # value raw, :number coerces). `inputs` stays the ordered name list either
105
+ # value raw, :number coerces, :boolean reads a checkbox's checked state —
106
+ # issue #262). `inputs` stays the ordered name list either
106
107
  # way, so iteration order is preserved and the array-form wire is unchanged.
107
108
  #
108
109
  # `mirror` (issue #159) is nil when undeclared, else a { name => [id
@@ -174,6 +174,22 @@ module Phlex
174
174
  coerced.equal?(DROP) ? {} : coerced
175
175
  end
176
176
 
177
+ # The key this DECLARATION holds for `name` if it declares it as an ARRAY
178
+ # param, else nil. Reads the declaration, never the incoming params, so the
179
+ # endpoint's empty-group announcement (issue #258) can only ever fill
180
+ # something the action asked for.
181
+ #
182
+ # A key that is neither a String nor a Symbol stays unresolved on purpose:
183
+ # `compile` accepts `{ 0 => [:string] }`, but `coerce_hash` raises on
184
+ # `0.to_sym` the moment that key is PRESENT, and the announcement is what
185
+ # would make it present. Resolving it would turn a request that answers 200
186
+ # and fills nothing into a 500.
187
+ def array_param(name)
188
+ name = name.to_s
189
+ key, type = @schema.find { |k, _| (k.is_a?(String) || k.is_a?(Symbol)) && k.to_s == name }
190
+ key if type.is_a?(Array)
191
+ end
192
+
177
193
  private
178
194
 
179
195
  # Coerce a value against a declared type. Arrays accept both a real JSON
@@ -64,10 +64,17 @@ module Phlex
64
64
  # `:file` param is present, issue #34): token + act flat, params bracketed.
65
65
  # No JSON Content-Type — Rails' test `post` builds the multipart body from
66
66
  # the nested Hash (files ride as UploadedFile values).
67
- def post_reactive_multipart(component_or_class, act, params: {}, payload: {})
67
+ #
68
+ # `empty_groups:` names the `[]` groups the client cleared (issue #258). A
69
+ # form body cannot carry an empty array, so the client leaves those keys
70
+ # out and announces them in a field of its own; passing the names here is
71
+ # how a spec reproduces that request. Omit it and the body is exactly what
72
+ # it was before the field existed.
73
+ def post_reactive_multipart(component_or_class, act, params: {}, payload: {}, empty_groups: [])
68
74
  token = reactive_action_token(component_or_class, payload)
69
- post Phlex::Reactive.action_path,
70
- params: { token:, act:, params: },
75
+ body = { token:, act:, params: }
76
+ body[:empty_groups] = empty_groups if empty_groups.present?
77
+ post Phlex::Reactive.action_path, params: body,
71
78
  headers: { "Accept" => "text/vnd.turbo-stream.html" }
72
79
  end
73
80
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Phlex
4
4
  module Reactive
5
- VERSION = "0.13.2"
5
+ VERSION = "0.13.3"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: phlex-reactive
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.2
4
+ version: 0.13.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikael Henriksson