phlex-reactive 0.10.0 → 0.11.0
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 +246 -0
- data/README.md +127 -78
- data/app/controllers/phlex/reactive/actions_controller.rb +28 -8
- data/app/javascript/phlex/reactive/confirm_predicate.js +52 -0
- data/app/javascript/phlex/reactive/confirm_predicate.min.js +4 -0
- data/app/javascript/phlex/reactive/confirm_predicate.min.js.map +10 -0
- data/app/javascript/phlex/reactive/reactive_controller.js +370 -179
- 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/dsl.rb +122 -34
- data/lib/phlex/reactive/component/helpers.rb +307 -165
- data/lib/phlex/reactive/component/registry.rb +6 -1
- data/lib/phlex/reactive/component.rb +3 -3
- data/lib/phlex/reactive/defer.rb +1 -1
- data/lib/phlex/reactive/deferred_render_job.rb +2 -2
- data/lib/phlex/reactive/engine.rb +14 -0
- data/lib/phlex/reactive/js.rb +29 -15
- data/lib/phlex/reactive/reply.rb +105 -44
- data/lib/phlex/reactive/response.rb +139 -48
- data/lib/phlex/reactive/streamable.rb +229 -199
- data/lib/phlex/reactive/test_helpers.rb +1 -1
- data/lib/phlex/reactive/version.rb +1 -1
- data/lib/phlex/reactive.rb +137 -12
- metadata +4 -1
data/lib/phlex/reactive.rb
CHANGED
|
@@ -279,6 +279,82 @@ module Phlex
|
|
|
279
279
|
@param_types_frozen = false
|
|
280
280
|
end
|
|
281
281
|
|
|
282
|
+
# Register (with a schema Hash) OR read (bare name) a NAMED param schema
|
|
283
|
+
# (issue #184) — a reusable, boot-declared schema so sibling components stop
|
|
284
|
+
# duplicating verbatim constants that drift. Follows the param_type contract:
|
|
285
|
+
# register in an INITIALIZER (the registry freezes after boot).
|
|
286
|
+
#
|
|
287
|
+
# # config/initializers/phlex_reactive.rb
|
|
288
|
+
# Phlex::Reactive.param_schema :todo, title: :string, done: :boolean
|
|
289
|
+
# # then: action :save, params: :todo
|
|
290
|
+
# # compose: action :bulk, params: { **Phlex::Reactive.param_schema(:todo), note: :string }
|
|
291
|
+
#
|
|
292
|
+
# Reading an unknown name raises, listing the registered ones. The stored
|
|
293
|
+
# schema is frozen; the reader returns it (compose with ** for a new Hash).
|
|
294
|
+
def param_schema(name, schema = nil)
|
|
295
|
+
return fetch_param_schema(name) if schema.nil?
|
|
296
|
+
|
|
297
|
+
if param_schemas_frozen?
|
|
298
|
+
raise Error, "Phlex::Reactive.param_schema(#{name.inspect}) called after boot — the param-schema " \
|
|
299
|
+
"registry is frozen once the app is initialized. Register named schemas in an " \
|
|
300
|
+
"initializer (config/initializers/phlex_reactive.rb)."
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
param_schemas[name.to_sym] = deep_freeze_schema(schema.transform_keys(&:to_sym))
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
def param_schemas
|
|
307
|
+
@param_schemas ||= {}
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def param_schema?(name)
|
|
311
|
+
param_schemas.key?(name.to_sym)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def freeze_param_schemas!
|
|
315
|
+
param_schemas.freeze
|
|
316
|
+
@param_schemas_frozen = true
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def param_schemas_frozen?
|
|
320
|
+
@param_schemas_frozen ||= false
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# Drop the named-schema registry and unfreeze it (tests only).
|
|
324
|
+
def reset_param_schemas!
|
|
325
|
+
@param_schemas = {}
|
|
326
|
+
@param_schemas_frozen = false
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
private
|
|
330
|
+
|
|
331
|
+
# Deep-freeze a registered schema so a nested Hash/Array (a nested-param
|
|
332
|
+
# schema like { address: { street: :string } }) can't be mutated through the
|
|
333
|
+
# memoized object fetch_param_schema returns — a top-level freeze alone leaves
|
|
334
|
+
# inner structures writable, poisoning the shared registry entry. Symbolizes
|
|
335
|
+
# nested keys too so composed-in nested schemas stay canonical.
|
|
336
|
+
def deep_freeze_schema(value)
|
|
337
|
+
case value
|
|
338
|
+
when Hash then value.to_h { |k, v| [k.to_sym, deep_freeze_schema(v)] }.freeze
|
|
339
|
+
when Array then value.map { deep_freeze_schema(it) }.freeze
|
|
340
|
+
else value
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# Resolve a registered named schema, raising a guided error (listing the
|
|
345
|
+
# registered names) for an unknown one — the action macro calls this to turn
|
|
346
|
+
# `params: :todo` into the schema Hash.
|
|
347
|
+
def fetch_param_schema(name)
|
|
348
|
+
param_schemas.fetch(name.to_sym) do
|
|
349
|
+
known = param_schemas.keys.sort.join(", ")
|
|
350
|
+
raise ::ArgumentError,
|
|
351
|
+
"Phlex::Reactive.param_schema(#{name.inspect}) is not registered. " \
|
|
352
|
+
"Registered schemas: #{known.empty? ? "(none)" : known}."
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
public
|
|
357
|
+
|
|
282
358
|
# Emit an `<event>.phlex_reactive` ActiveSupport::Notifications event around
|
|
283
359
|
# a block, yielding the mutable payload so a rescue can finalize the outcome
|
|
284
360
|
# (issue #107). ASN.instrument is cheap when nothing is subscribed (the hot
|
|
@@ -560,13 +636,29 @@ module Phlex
|
|
|
560
636
|
# diagnostic still goes to the log.
|
|
561
637
|
attr_accessor :error_flash
|
|
562
638
|
|
|
563
|
-
#
|
|
564
|
-
#
|
|
565
|
-
#
|
|
566
|
-
#
|
|
567
|
-
#
|
|
639
|
+
# A CALLABLE that builds a flash component from STRING flash content (issue
|
|
640
|
+
# #182). Given (level, content), it returns a Phlex component the gem renders
|
|
641
|
+
# in place of the built-in <div class="reactive-flash reactive-flash--{level}">
|
|
642
|
+
# wrapper — so the APP owns the kwarg mapping (no hardcoded new(level:,
|
|
643
|
+
# content:) contract that collides with a real app's flash component):
|
|
644
|
+
#
|
|
645
|
+
# Phlex::Reactive.flash_component = ->(level, content) { MyFlash.new(level:, message: content) }
|
|
646
|
+
#
|
|
647
|
+
# Default nil (the built-in wrapper). Phlex component content passed to
|
|
568
648
|
# reply.flash always renders verbatim and bypasses this.
|
|
569
|
-
|
|
649
|
+
attr_reader :flash_component
|
|
650
|
+
|
|
651
|
+
# Reject a bare Class (the pre-#182 contract) with the exact lambda rewrite —
|
|
652
|
+
# the gem no longer guesses the component's kwargs.
|
|
653
|
+
def flash_component=(callable)
|
|
654
|
+
if callable.is_a?(Class)
|
|
655
|
+
raise ArgumentError,
|
|
656
|
+
"Phlex::Reactive.flash_component is now a callable (issue #182) — " \
|
|
657
|
+
"flash_component = ->(level, content) { #{callable}.new(level:, content:) }"
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
@flash_component = callable
|
|
661
|
+
end
|
|
570
662
|
|
|
571
663
|
# Render a Phlex component to HTML with a full (off-request) view context.
|
|
572
664
|
# Uses phlex-rails' #render_in against the memoized view context — a direct
|
|
@@ -581,10 +673,36 @@ module Phlex
|
|
|
581
673
|
Phlex::Reactive::Defer.with_real_render { component.render_in(off_request_view_context) }
|
|
582
674
|
end
|
|
583
675
|
|
|
676
|
+
# Module-level broadcast (issue #185) — the same broadcast_to as the class
|
|
677
|
+
# form, for a BUILT component payload, so a NON-Streamable target (a count
|
|
678
|
+
# badge, any plain Phlex component) broadcasts WITHOUT hand-rolling the raw
|
|
679
|
+
# channel + render, and IS instrumented:
|
|
680
|
+
#
|
|
681
|
+
# Phlex::Reactive.broadcast_to(@list, :todos, update: TodoCount.new(list: @list), target: "todos-count")
|
|
682
|
+
# Phlex::Reactive.broadcast_to(user, :alerts, replace: NotificationsBadge.new(user:))
|
|
683
|
+
#
|
|
684
|
+
# Container verbs (update:/append:/prepend:) take any component; self-targeting
|
|
685
|
+
# verbs (replace:/remove:) require a Streamable payload (its #id is the target)
|
|
686
|
+
# — a plain component gets a guided error steering to update:. Shares the one
|
|
687
|
+
# broadcast implementation with the class-level form.
|
|
688
|
+
def broadcast_to(*streamables, morph: false, target: nil, exclude: nil, visible_to: nil, each: nil, **verb)
|
|
689
|
+
name, payload = Phlex::Reactive::Streamable.extract_module_broadcast_verb(verb)
|
|
690
|
+
component = name == :js ? nil : payload
|
|
691
|
+
keys = each ? each.map { Array(it) } : [streamables]
|
|
692
|
+
# The owner is the payload's class when Streamable (for js ops + instrument),
|
|
693
|
+
# else Streamable itself (a plain component still instruments via the shared
|
|
694
|
+
# path). A nil payload (a bare state-backed default) isn't supported here —
|
|
695
|
+
# the module form is for BUILT component payloads.
|
|
696
|
+
owner = payload.is_a?(Phlex::Reactive::Streamable) ? payload.class : Phlex::Reactive::Streamable
|
|
697
|
+
Phlex::Reactive::Streamable.broadcast_component(
|
|
698
|
+
owner, name, payload, component, keys, morph:, target:, exclude:, visible_to:
|
|
699
|
+
)
|
|
700
|
+
end
|
|
701
|
+
|
|
584
702
|
# A Turbo::Streams::TagBuilder bound to an off-request view context, used
|
|
585
703
|
# to build standalone streams not tied to a specific component's id — a
|
|
586
704
|
# Response flash append, a reactive_collection row removal, a count
|
|
587
|
-
# companion update, an
|
|
705
|
+
# companion update, an also() companion. Cached PER THREAD alongside
|
|
588
706
|
# the context it's bound to (see off_request_view_context for why
|
|
589
707
|
# per-thread). Renamed from flash_builder (issue #113): the builder does
|
|
590
708
|
# far more than flashes, so the name misled. flash_builder stays a
|
|
@@ -615,11 +733,18 @@ module Phlex
|
|
|
615
733
|
@off_request_view_context_generation = off_request_view_context_generation + 1
|
|
616
734
|
end
|
|
617
735
|
|
|
618
|
-
#
|
|
619
|
-
#
|
|
620
|
-
#
|
|
621
|
-
|
|
622
|
-
|
|
736
|
+
# Issue #182: the pre-#113 aliases are removed (they contradicted the
|
|
737
|
+
# clean-break rule — two names for one thing). Each raises a guided error
|
|
738
|
+
# naming the real method. The engine (to_prepare) already uses the new names.
|
|
739
|
+
def flash_builder
|
|
740
|
+
raise NoMethodError,
|
|
741
|
+
"Phlex::Reactive.flash_builder was removed in issue #182 — use stream_builder"
|
|
742
|
+
end
|
|
743
|
+
|
|
744
|
+
def reset_flash_builder!
|
|
745
|
+
raise NoMethodError,
|
|
746
|
+
"Phlex::Reactive.reset_flash_builder! was removed in issue #182 — use reset_stream_builder!"
|
|
747
|
+
end
|
|
623
748
|
|
|
624
749
|
def off_request_view_context_generation
|
|
625
750
|
@off_request_view_context_generation ||= 0
|
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.
|
|
4
|
+
version: 0.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mikael Henriksson
|
|
@@ -123,6 +123,9 @@ files:
|
|
|
123
123
|
- app/javascript/phlex/reactive/confirm.js
|
|
124
124
|
- app/javascript/phlex/reactive/confirm.min.js
|
|
125
125
|
- app/javascript/phlex/reactive/confirm.min.js.map
|
|
126
|
+
- app/javascript/phlex/reactive/confirm_predicate.js
|
|
127
|
+
- app/javascript/phlex/reactive/confirm_predicate.min.js
|
|
128
|
+
- app/javascript/phlex/reactive/confirm_predicate.min.js.map
|
|
126
129
|
- app/javascript/phlex/reactive/inspect.js
|
|
127
130
|
- app/javascript/phlex/reactive/inspect.min.js
|
|
128
131
|
- app/javascript/phlex/reactive/inspect.min.js.map
|