phlex-reactive 0.11.5 → 0.11.6
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 +4 -4
- data/CHANGELOG.md +32 -0
- data/README.md +49 -2
- data/app/javascript/phlex/reactive/reactive_controller.js +206 -4
- data/app/javascript/phlex/reactive/reactive_controller.min.js +2 -2
- data/app/javascript/phlex/reactive/reactive_controller.min.js.map +3 -3
- data/lib/phlex/reactive/component/helpers.rb +82 -10
- data/lib/phlex/reactive/version.rb +1 -1
- metadata +1 -1
|
@@ -793,8 +793,34 @@ module Phlex
|
|
|
793
793
|
# The container cloned rows land in — one per association, inside the
|
|
794
794
|
# root. Server-rendered rows (an edit form's persisted children) render
|
|
795
795
|
# inside it too.
|
|
796
|
-
|
|
797
|
-
|
|
796
|
+
#
|
|
797
|
+
# `as: :json` (issue #208) switches the SUBMIT wire from Rails'
|
|
798
|
+
# accepts_nested_attributes_for names to ONE hidden JSON field — for an
|
|
799
|
+
# app whose controller already parses a serialized JSON param
|
|
800
|
+
# (`JSON.parse(params[:order][:todos])`) instead of nested attributes.
|
|
801
|
+
# The container keeps its plain marker (nestedAdd/Remove still key on
|
|
802
|
+
# it), and gains data-reactive-nested-json plus a scope-aware selector
|
|
803
|
+
# naming the hidden field the client mirrors the rows into on every
|
|
804
|
+
# add/remove/input. The default (:attributes) is unchanged — the plain
|
|
805
|
+
# accepts_nested_attributes_for wire.
|
|
806
|
+
def reactive_nested_list(association, as: :attributes)
|
|
807
|
+
name = nested_identifier!(:reactive_nested_list, :association, association)
|
|
808
|
+
data = { reactive_nested_list: name }
|
|
809
|
+
return { data: } if as == :attributes
|
|
810
|
+
|
|
811
|
+
unless as == :json
|
|
812
|
+
raise ArgumentError,
|
|
813
|
+
"reactive_nested_list(as:) takes :attributes (the default, Rails nested-attribute names) " \
|
|
814
|
+
"or :json (serialize the rows into one hidden JSON field), got #{as.inspect}"
|
|
815
|
+
end
|
|
816
|
+
|
|
817
|
+
# JSON mode: mark the container and name the hidden field the client
|
|
818
|
+
# keeps in sync — a scope-aware [name="…"] selector, the same
|
|
819
|
+
# convention reactive_tags/reactive_filter use so the field resolves
|
|
820
|
+
# under reactive_scope too.
|
|
821
|
+
data[:reactive_nested_json] = name
|
|
822
|
+
data[:reactive_nested_json_field] = %([name="#{scoped_field_name(association)}"])
|
|
823
|
+
{ data: }
|
|
798
824
|
end
|
|
799
825
|
|
|
800
826
|
# The <template> holding ONE row's markup (server-owned, inert until
|
|
@@ -864,6 +890,22 @@ module Phlex
|
|
|
864
890
|
#
|
|
865
891
|
# reactive_show_targets(mode: { "#advanced-tab" => "advanced" },
|
|
866
892
|
# kind: { "#premium-note" => %w[gold platinum] })
|
|
893
|
+
#
|
|
894
|
+
# MULTI-FIELD targets (issue #209): a "#id" KEY takes a full
|
|
895
|
+
# if:/if_any:/unless: conditions Hash — the SAME language reactive_show
|
|
896
|
+
# speaks, so a cross-root target can finally read a COMBINATION of
|
|
897
|
+
# owned fields (the last case forcing a bespoke JS listener):
|
|
898
|
+
#
|
|
899
|
+
# reactive_show_targets("#trade-warning" => {
|
|
900
|
+
# if: { type: "trade", price: ..0 } # type == "trade" AND price <= 0
|
|
901
|
+
# })
|
|
902
|
+
#
|
|
903
|
+
# Target-keyed and field-keyed entries mix in the ONE call (a "#" key is
|
|
904
|
+
# unambiguous — a field name may never start with "#"). The client folds
|
|
905
|
+
# the target's DNF payload with the same per-term field reads as an
|
|
906
|
+
# in-root reactive_show: every referenced field must be OWNED by this
|
|
907
|
+
# root (a missing owned field reads as blank, fail-closed); a target
|
|
908
|
+
# whose fields are ALL unowned is left alone, like the single-field skip.
|
|
867
909
|
def reactive_show_targets(field, targets = nil)
|
|
868
910
|
field_maps = targets.nil? ? field : { field => targets }
|
|
869
911
|
unless field_maps.is_a?(Hash) && field_maps.any?
|
|
@@ -873,16 +915,12 @@ module Phlex
|
|
|
873
915
|
end
|
|
874
916
|
|
|
875
917
|
normalized = field_maps.to_h do |name, map|
|
|
876
|
-
# Catch the forgotten-field-name misuse — reactive_show_targets(
|
|
877
|
-
# "#id" => {…}) — before the per-target validation turns it into a
|
|
878
|
-
# baffling "predicate" error.
|
|
879
918
|
if name.to_s.start_with?("#")
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
919
|
+
# Target-keyed conditions (issue #209): "#id" => { if:/if_any:/unless: }.
|
|
920
|
+
[name.to_s, normalize_show_target_conditions(name.to_s, map)]
|
|
921
|
+
else
|
|
922
|
+
[name.to_s, normalize_show_target_map(name, map)]
|
|
883
923
|
end
|
|
884
|
-
|
|
885
|
-
[name.to_s, normalize_show_target_map(name, map)]
|
|
886
924
|
end
|
|
887
925
|
|
|
888
926
|
{ data: { reactive_show_targets: normalized.to_json } }
|
|
@@ -1122,6 +1160,40 @@ module Phlex
|
|
|
1122
1160
|
end
|
|
1123
1161
|
end
|
|
1124
1162
|
|
|
1163
|
+
# Normalize + validate ONE target-keyed entry (issue #209): the "#id"
|
|
1164
|
+
# key is a single id selector (the same declare-time guard as
|
|
1165
|
+
# field-keyed targets), the value a full if:/if_any:/unless: conditions
|
|
1166
|
+
# Hash compiled by ShowConditions into the SAME { "any" => groups } DNF
|
|
1167
|
+
# payload reactive_show emits. Anything else — a bare value, unknown
|
|
1168
|
+
# keys, empty conditions — raises a guided error at render (a dead
|
|
1169
|
+
# binding must never reach the browser).
|
|
1170
|
+
def normalize_show_target_conditions(selector, conditions)
|
|
1171
|
+
unless selector.match?(DSL::MIRROR_ID_SELECTOR)
|
|
1172
|
+
raise ArgumentError,
|
|
1173
|
+
"reactive_show_targets target #{selector.inspect} must be a single " \
|
|
1174
|
+
"ID selector (\"#id\") — cross-root visibility is id-allowlisted, like mirror: (#159)"
|
|
1175
|
+
end
|
|
1176
|
+
unless conditions.is_a?(Hash) && conditions.any?
|
|
1177
|
+
raise ArgumentError,
|
|
1178
|
+
"reactive_show_targets: a target key takes a conditions Hash — " \
|
|
1179
|
+
"reactive_show_targets(#{selector.inspect} => { if: { field: value, ... } }); " \
|
|
1180
|
+
"to key by field instead: reactive_show_targets(:field, #{selector.inspect} => value). " \
|
|
1181
|
+
"Got #{selector.inspect} => #{conditions.inspect}"
|
|
1182
|
+
end
|
|
1183
|
+
# Unknown keys are reported BEFORE the presence check so { bogus: 1 }
|
|
1184
|
+
# names its offender instead of the generic shape message (specific
|
|
1185
|
+
# beats generic). A non-empty hash surviving this subtraction holds
|
|
1186
|
+
# only condition keys, so no separate presence check remains.
|
|
1187
|
+
if (unknown = conditions.keys - SHOW_CONDITION_KEYS).any?
|
|
1188
|
+
raise ArgumentError,
|
|
1189
|
+
"reactive_show_targets #{selector.inspect}: unknown conditions key(s) " \
|
|
1190
|
+
"#{unknown.map(&:inspect).join(", ")} — a target's conditions Hash takes only " \
|
|
1191
|
+
"if:/if_any:/unless: (the reactive_show language)"
|
|
1192
|
+
end
|
|
1193
|
+
|
|
1194
|
+
{ "any" => Phlex::Reactive::ShowConditions.normalize(**conditions) }
|
|
1195
|
+
end
|
|
1196
|
+
|
|
1125
1197
|
# Reject the removed 0.9.5 reactive_show surface (issue #180 clean break)
|
|
1126
1198
|
# with a GUIDED error printing the if:/if_any:/unless: rewrite. A
|
|
1127
1199
|
# positional field, a predicate kwarg (equals:/not:/in:/gte:/…), or a
|