phlex-reactive 0.9.2 → 0.9.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/README.md +52 -0
- data/app/javascript/phlex/reactive/reactive_controller.js +121 -0
- 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 +55 -0
- data/lib/phlex/reactive/version.rb +1 -1
- metadata +1 -1
|
@@ -399,6 +399,54 @@ module Phlex
|
|
|
399
399
|
span(**mix({ data: { reactive_text: name.to_s } }, attrs)) { initial }
|
|
400
400
|
end
|
|
401
401
|
|
|
402
|
+
# Value-conditional visibility (issue #161) — the x-show / data-show /
|
|
403
|
+
# wire:show equivalent, entirely client-side. Spread onto the element to
|
|
404
|
+
# show/hide; it declares which OWNED field controls it plus ONE literal
|
|
405
|
+
# predicate, and the generic controller toggles the `hidden` attribute
|
|
406
|
+
# from the field's CURRENT value on every input/change — no round trip,
|
|
407
|
+
# no token, no bespoke Stimulus controller:
|
|
408
|
+
#
|
|
409
|
+
# div(**reactive_show(:mode, not: "off")) { "details" }
|
|
410
|
+
# div(**reactive_show(:kind, equals: "premium")) { "premium panel" }
|
|
411
|
+
# div(**reactive_show(:size, in: %w[l xl])) { "surcharge note" }
|
|
412
|
+
# div(**reactive_show(:gift, equals: true)) { "gift message" }
|
|
413
|
+
#
|
|
414
|
+
# Exactly one predicate — equals:, not:, or in: (a list) — and every
|
|
415
|
+
# value is STRINGIFIED for a literal match against the field's value
|
|
416
|
+
# (a checkbox compares its checked state as "true"/"false", so
|
|
417
|
+
# `equals: true` is the checkbox form; a radio group reads the checked
|
|
418
|
+
# radio's value). This is a DECLARED LITERAL MATCH, never an expression
|
|
419
|
+
# — there is no eval surface (default-deny, like the op vocabulary).
|
|
420
|
+
#
|
|
421
|
+
# Scope: presentational only, strictly less powerful than the js ops —
|
|
422
|
+
# it reads an owned field (#15 ownership) and toggles `hidden` on an
|
|
423
|
+
# owned element. The client seeds visibility at connect and reconciles
|
|
424
|
+
# after a morph; render the initial `hidden:` yourself (from the same
|
|
425
|
+
# server state that renders the field) to avoid a first-paint flash.
|
|
426
|
+
# Extra attrs deep-merge over the binding (mix), like reactive_field.
|
|
427
|
+
def reactive_show(field, **options)
|
|
428
|
+
predicates = options.slice(*SHOW_PREDICATE_KEYS)
|
|
429
|
+
attrs = options.except(*SHOW_PREDICATE_KEYS)
|
|
430
|
+
unless predicates.size == 1
|
|
431
|
+
raise ArgumentError,
|
|
432
|
+
"reactive_show(#{field.inspect}) needs exactly one predicate — equals:, not:, or " \
|
|
433
|
+
"in: — got #{predicates.keys.inspect}"
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
key, value = predicates.first
|
|
437
|
+
data = { reactive_show_field: field.to_s }
|
|
438
|
+
if key == :in
|
|
439
|
+
list = Array(value).map(&:to_s)
|
|
440
|
+
raise ArgumentError, "reactive_show(#{field.inspect}) in: needs at least one value" if list.empty?
|
|
441
|
+
|
|
442
|
+
data[:reactive_show_in] = list.to_json
|
|
443
|
+
else
|
|
444
|
+
data[:"reactive_show_#{key}"] = value.to_s
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
mix({ data: }, attrs)
|
|
448
|
+
end
|
|
449
|
+
|
|
402
450
|
# Scoped busy indicator (issue #99). Marks an element so the generic
|
|
403
451
|
# controller toggles `data-reactive-busy` on it ONLY while `action` is in
|
|
404
452
|
# flight — the scoped sibling of the always-on `data-reactive-busy` the
|
|
@@ -485,6 +533,13 @@ module Phlex
|
|
|
485
533
|
reactive_record_for_nested.update!(**nested_attributes(association, attrs), **extra)
|
|
486
534
|
end
|
|
487
535
|
|
|
536
|
+
# The declared reactive_show predicates (issue #161): a literal match on
|
|
537
|
+
# the controlling field's current value. Exactly one per binding —
|
|
538
|
+
# enforced loudly at render (a dead binding must not no-op in the
|
|
539
|
+
# browser). `not`/`in` are Ruby keywords, so they arrive via **options
|
|
540
|
+
# rather than named kwargs.
|
|
541
|
+
SHOW_PREDICATE_KEYS = %i[equals not in].freeze
|
|
542
|
+
|
|
488
543
|
# The declared optimistic-hint class ops (issue #98): the cosmetic class
|
|
489
544
|
# vocabulary the client applies instantly and reverts on failure. Enforced
|
|
490
545
|
# at build time in optimistic_hint_json (default-deny — a dead hint fails
|