phlex-reactive 0.16.0 → 0.16.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 +4 -4
- data/CHANGELOG.md +247 -145
- data/README.md +85 -1
- data/app/controllers/phlex/reactive/actions_controller.rb +10 -0
- data/app/javascript/phlex/reactive/core.js +1 -0
- data/app/javascript/phlex/reactive/core.min.js +4 -4
- data/app/javascript/phlex/reactive/core.min.js.map +4 -4
- data/app/javascript/phlex/reactive/features/bindings.js +81 -4
- data/app/javascript/phlex/reactive/features/bindings.min.js +2 -2
- data/app/javascript/phlex/reactive/features/bindings.min.js.map +3 -3
- data/app/javascript/phlex/reactive/features/drag.js +522 -0
- data/app/javascript/phlex/reactive/features/drag.min.js +4 -0
- data/app/javascript/phlex/reactive/features/drag.min.js.map +10 -0
- data/app/javascript/phlex/reactive/reactive_controller.js +2 -0
- data/app/javascript/phlex/reactive/reactive_controller.min.js +4 -4
- data/app/javascript/phlex/reactive/reactive_controller.min.js.map +7 -6
- data/app/javascript/phlex/reactive/runtime.js +57 -23
- data/lib/phlex/reactive/component/css_vars_binding.rb +118 -0
- data/lib/phlex/reactive/component/drag.rb +270 -0
- data/lib/phlex/reactive/component/helpers.rb +5 -1
- data/lib/phlex/reactive/engine.rb +6 -4
- data/lib/phlex/reactive/held_fields.rb +21 -1
- data/lib/phlex/reactive/response.rb +55 -4
- data/lib/phlex/reactive/test_helpers.rb +6 -0
- data/lib/phlex/reactive/version.rb +1 -1
- data/sig/phlex/reactive/response.rbs +3 -0
- metadata +6 -1
|
@@ -14,9 +14,15 @@ module Phlex
|
|
|
14
14
|
#
|
|
15
15
|
# def save(title: nil)
|
|
16
16
|
# authorize!(@note, to: :update?)
|
|
17
|
-
# return
|
|
17
|
+
# return reply.morph.refused("title") if reactive_held_fields.include?("title") && title != @note.title
|
|
18
18
|
# ...
|
|
19
19
|
#
|
|
20
|
+
# A reply to the root's own request releases its marks: it answers exactly
|
|
21
|
+
# those values. A refusal (`reply.morph.refused`, issue #413) is sent back
|
|
22
|
+
# in the X-Reactive-Refused header, and the client keeps the marks it names,
|
|
23
|
+
# so the user's next keystroke is refused too until the action stores the
|
|
24
|
+
# field or the user takes the other value.
|
|
25
|
+
#
|
|
20
26
|
# The header is UNTRUSTED input, like every request field: it carries names
|
|
21
27
|
# only, never values, and is read as data — capped in bytes, count and
|
|
22
28
|
# length, non-strings dropped, and filtered to the names the running action
|
|
@@ -26,6 +32,8 @@ module Phlex
|
|
|
26
32
|
# It is a hint about what the user saw, not authorization.
|
|
27
33
|
module HeldFields
|
|
28
34
|
HEADER = "X-Reactive-Held"
|
|
35
|
+
# The response header a refusing reply sends (issue #413).
|
|
36
|
+
REFUSED_HEADER = "X-Reactive-Refused"
|
|
29
37
|
|
|
30
38
|
# A raw header longer than this is ignored unparsed.
|
|
31
39
|
MAX_BYTES = 4_096
|
|
@@ -62,6 +70,18 @@ module Phlex
|
|
|
62
70
|
EMPTY
|
|
63
71
|
end
|
|
64
72
|
|
|
73
|
+
# The X-Reactive-Refused value for Response#refused_fields: "*" keeps
|
|
74
|
+
# every mark, a JSON list keeps those names. ASCII only (a header is a
|
|
75
|
+
# byte string), as the client escapes X-Reactive-Held. A list past
|
|
76
|
+
# MAX_BYTES (a header a proxy may refuse) widens to "*": keeping more
|
|
77
|
+
# marks than named is safe, losing the refusal is not.
|
|
78
|
+
def refused_header(fields)
|
|
79
|
+
return "*" if fields == :all
|
|
80
|
+
|
|
81
|
+
json = JSON.generate(fields, ascii_only: true)
|
|
82
|
+
json.bytesize > MAX_BYTES ? "*" : json
|
|
83
|
+
end
|
|
84
|
+
|
|
65
85
|
# Keep the strings among `names` that `action_def` declares as params.
|
|
66
86
|
def filter(names, action_def)
|
|
67
87
|
declared = declared_names(action_def)
|
|
@@ -19,6 +19,7 @@ module Phlex
|
|
|
19
19
|
# reply.remove # drop the element (e.g. moderation queue)
|
|
20
20
|
# reply.redirect(article_url(@article)) # slug changed -> Turbo.visit the new URL
|
|
21
21
|
# reply.replace.stream(Totals.update(@order)) # multi-stream
|
|
22
|
+
# reply.morph.refused("title") # answered, not stored: keep the field held
|
|
22
23
|
class Response
|
|
23
24
|
attr_reader :streams, :redirect_url, :token_component, :subject_component
|
|
24
25
|
|
|
@@ -351,8 +352,11 @@ data-reactive-ops="#{ERB::Util.html_escape(json)}"></turbo-stream>).html_safe
|
|
|
351
352
|
# reply never allocates an empty array per Response.
|
|
352
353
|
NO_SEGMENTS = [].freeze
|
|
353
354
|
|
|
355
|
+
# refused: what #refused recorded (issue #413) — nil, :all, or a frozen
|
|
356
|
+
# list of field names.
|
|
354
357
|
def initialize(streams: [], redirect_url: nil, render_self: true, token_component: nil,
|
|
355
|
-
subject_component: nil, deferred_segments: NO_SEGMENTS, pending_segments: NO_SEGMENTS
|
|
358
|
+
subject_component: nil, deferred_segments: NO_SEGMENTS, pending_segments: NO_SEGMENTS,
|
|
359
|
+
refused: nil)
|
|
356
360
|
@streams = streams.freeze
|
|
357
361
|
@redirect_url = redirect_url
|
|
358
362
|
@render_self = render_self
|
|
@@ -360,9 +364,14 @@ data-reactive-ops="#{ERB::Util.html_escape(json)}"></turbo-stream>).html_safe
|
|
|
360
364
|
@subject_component = subject_component
|
|
361
365
|
@deferred_segments = deferred_segments.freeze
|
|
362
366
|
@pending_segments = pending_segments.freeze
|
|
367
|
+
@refused_fields = refused
|
|
363
368
|
freeze
|
|
364
369
|
end
|
|
365
370
|
|
|
371
|
+
# What the reply refused (issue #413): :all, the field names, or nil when
|
|
372
|
+
# it refused nothing.
|
|
373
|
+
attr_reader :refused_fields
|
|
374
|
+
|
|
366
375
|
# The recorded reply.defer segments (issue #165), in call order. The
|
|
367
376
|
# Response only RECORDS them; the endpoint (via the Defer module) builds
|
|
368
377
|
# the placeholder + directive streams AFTER the action's transaction
|
|
@@ -388,7 +397,8 @@ data-reactive-ops="#{ERB::Util.html_escape(json)}"></turbo-stream>).html_safe
|
|
|
388
397
|
token_component: @token_component,
|
|
389
398
|
subject_component: @subject_component,
|
|
390
399
|
deferred_segments: @deferred_segments,
|
|
391
|
-
pending_segments: @pending_segments
|
|
400
|
+
pending_segments: @pending_segments,
|
|
401
|
+
refused: @refused_fields
|
|
392
402
|
)
|
|
393
403
|
end
|
|
394
404
|
|
|
@@ -420,7 +430,8 @@ data-reactive-ops="#{ERB::Util.html_escape(json)}"></turbo-stream>).html_safe
|
|
|
420
430
|
subject_component: @subject_component,
|
|
421
431
|
deferred_segments: @deferred_segments +
|
|
422
432
|
[Phlex::Reactive::Defer::Segment.new(component:, placeholder:, morph:)],
|
|
423
|
-
pending_segments: @pending_segments
|
|
433
|
+
pending_segments: @pending_segments,
|
|
434
|
+
refused: @refused_fields
|
|
424
435
|
)
|
|
425
436
|
end
|
|
426
437
|
|
|
@@ -464,10 +475,50 @@ data-reactive-ops="#{ERB::Util.html_escape(json)}"></turbo-stream>).html_safe
|
|
|
464
475
|
token_component: @token_component || built.token_component,
|
|
465
476
|
subject_component: @subject_component,
|
|
466
477
|
deferred_segments: @deferred_segments,
|
|
467
|
-
pending_segments: @pending_segments + built.pending_segments
|
|
478
|
+
pending_segments: @pending_segments + built.pending_segments,
|
|
479
|
+
refused: @refused_fields
|
|
480
|
+
)
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
# Answer the posted values without storing them (issue #413). Every reply
|
|
484
|
+
# to a root's own request releases the fields a morph held (issue #370),
|
|
485
|
+
# because it answers exactly those values. A reply that refused them says
|
|
486
|
+
# so, and the client keeps the marks: the root's next action names the
|
|
487
|
+
# same fields in X-Reactive-Held again, instead of saving over the other
|
|
488
|
+
# change on the user's next keystroke.
|
|
489
|
+
#
|
|
490
|
+
# if reactive_held_fields.include?("title") && title != @note.title
|
|
491
|
+
# @conflict = title
|
|
492
|
+
# return reply.morph.refused("title")
|
|
493
|
+
# end
|
|
494
|
+
#
|
|
495
|
+
# No names keeps every mark the root holds; names (the fields' `name`
|
|
496
|
+
# attributes, as reactive_held_fields reports them) keep only those, and
|
|
497
|
+
# the root's other marks release as usual. Nothing else about the reply
|
|
498
|
+
# changes: it is a 200, with no error marker and no reactive:error.
|
|
499
|
+
# Returns a NEW Response (immutable). Dead on a redirect, like #defer.
|
|
500
|
+
def refused(*names)
|
|
501
|
+
if redirect?
|
|
502
|
+
raise Phlex::Reactive::Error,
|
|
503
|
+
"reply.refused on a redirect reply is dead — the client is navigating away, " \
|
|
504
|
+
"so there is no held field left to keep"
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
names = names.flatten.map { -it.to_s }.uniq.freeze
|
|
508
|
+
self.class.new(
|
|
509
|
+
streams: @streams,
|
|
510
|
+
redirect_url: @redirect_url,
|
|
511
|
+
render_self: @render_self,
|
|
512
|
+
token_component: @token_component,
|
|
513
|
+
subject_component: @subject_component,
|
|
514
|
+
deferred_segments: @deferred_segments,
|
|
515
|
+
pending_segments: @pending_segments,
|
|
516
|
+
refused: names.empty? ? :all : names
|
|
468
517
|
)
|
|
469
518
|
end
|
|
470
519
|
|
|
520
|
+
def refused? = !@refused_fields.nil?
|
|
521
|
+
|
|
471
522
|
# Chain a `reactive:js` op stream — server-pushed client DOM ops (issue
|
|
472
523
|
# #97) — onto this reply, so a focus/dispatch/class toggle runs on the
|
|
473
524
|
# client after the render applies:
|
|
@@ -271,6 +271,12 @@ module Phlex
|
|
|
271
271
|
@response&.redirect_url
|
|
272
272
|
end
|
|
273
273
|
|
|
274
|
+
# The action answered without storing (reply.morph.refused, issue #413):
|
|
275
|
+
# the client keeps the fields it held.
|
|
276
|
+
def refused?
|
|
277
|
+
!@response.nil? && @response.refused?
|
|
278
|
+
end
|
|
279
|
+
|
|
274
280
|
# The actor's turbo-stream strings, exactly as the endpoint would render
|
|
275
281
|
# them (response_streams) — a legacy return is the single self-replace; a
|
|
276
282
|
# Response is honored, with the same token-refresh streams appended.
|
|
@@ -9,6 +9,7 @@ module Phlex
|
|
|
9
9
|
attr_reader subject_component: untyped
|
|
10
10
|
attr_reader deferred_segments: Array[untyped]
|
|
11
11
|
attr_reader pending_segments: Array[untyped]
|
|
12
|
+
attr_reader refused_fields: (:all | Array[String])?
|
|
12
13
|
|
|
13
14
|
# --- chaining ---
|
|
14
15
|
def stream: (*String more) -> Response
|
|
@@ -17,6 +18,7 @@ module Phlex
|
|
|
17
18
|
def also: (?untyped companion, ?morph: bool, **untyped targets) -> Response
|
|
18
19
|
def defer: (untyped component, ?placeholder: untyped, ?morph: bool) -> Response
|
|
19
20
|
def pending: (untyped records, ?peers: bool, ?job: untyped, ?args: untyped, **untyped opts) ?{ (?) -> untyped } -> Response
|
|
21
|
+
def refused: (*(String | Symbol | Array[String | Symbol]) names) -> Response
|
|
20
22
|
|
|
21
23
|
# Removed: each raises a guided error naming its replacement.
|
|
22
24
|
def also_update: (untyped target, **untyped) -> bot
|
|
@@ -28,6 +30,7 @@ module Phlex
|
|
|
28
30
|
def refresh_token?: () -> bool
|
|
29
31
|
def deferred?: () -> bool
|
|
30
32
|
def pending?: () -> bool
|
|
33
|
+
def refused?: () -> bool
|
|
31
34
|
|
|
32
35
|
# --- the builders `reply` delegates to ---
|
|
33
36
|
def self.build_replace: (untyped component, ?morph: bool, ?effect: untyped) -> Response
|
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.16.
|
|
4
|
+
version: 0.16.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mikael Henriksson
|
|
@@ -149,6 +149,9 @@ files:
|
|
|
149
149
|
- app/javascript/phlex/reactive/features/devtools.js
|
|
150
150
|
- app/javascript/phlex/reactive/features/devtools.min.js
|
|
151
151
|
- app/javascript/phlex/reactive/features/devtools.min.js.map
|
|
152
|
+
- app/javascript/phlex/reactive/features/drag.js
|
|
153
|
+
- app/javascript/phlex/reactive/features/drag.min.js
|
|
154
|
+
- app/javascript/phlex/reactive/features/drag.min.js.map
|
|
152
155
|
- app/javascript/phlex/reactive/features/effects.js
|
|
153
156
|
- app/javascript/phlex/reactive/features/effects.min.js
|
|
154
157
|
- app/javascript/phlex/reactive/features/effects.min.js.map
|
|
@@ -197,6 +200,8 @@ files:
|
|
|
197
200
|
- lib/phlex/reactive/collections.rb
|
|
198
201
|
- lib/phlex/reactive/component.rb
|
|
199
202
|
- lib/phlex/reactive/component/countdown_binding.rb
|
|
203
|
+
- lib/phlex/reactive/component/css_vars_binding.rb
|
|
204
|
+
- lib/phlex/reactive/component/drag.rb
|
|
200
205
|
- lib/phlex/reactive/component/dsl.rb
|
|
201
206
|
- lib/phlex/reactive/component/editable.rb
|
|
202
207
|
- lib/phlex/reactive/component/field_references.rb
|