phlex-reactive 0.14.4 → 0.15.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 +70 -0
- data/README.md +191 -1
- data/app/controllers/phlex/reactive/actions_controller.rb +28 -18
- data/app/javascript/phlex/reactive/core.js +5 -5
- data/app/javascript/phlex/reactive/core.min.js +3 -3
- data/app/javascript/phlex/reactive/core.min.js.map +4 -4
- data/app/javascript/phlex/reactive/early.js +28 -9
- data/app/javascript/phlex/reactive/early.min.js +2 -2
- data/app/javascript/phlex/reactive/early.min.js.map +3 -3
- data/app/javascript/phlex/reactive/features/bindings.js +125 -140
- 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/form.js +63 -41
- data/app/javascript/phlex/reactive/features/form.min.js +2 -2
- data/app/javascript/phlex/reactive/features/form.min.js.map +3 -3
- data/app/javascript/phlex/reactive/features/presence.js +205 -0
- data/app/javascript/phlex/reactive/features/presence.min.js +4 -0
- data/app/javascript/phlex/reactive/features/presence.min.js.map +10 -0
- data/app/javascript/phlex/reactive/inspect.js +5 -1
- data/app/javascript/phlex/reactive/inspect.min.js +2 -2
- data/app/javascript/phlex/reactive/inspect.min.js.map +3 -3
- data/app/javascript/phlex/reactive/reactive_controller.js +3 -1
- data/app/javascript/phlex/reactive/reactive_controller.min.js +3 -3
- data/app/javascript/phlex/reactive/reactive_controller.min.js.map +8 -7
- data/app/javascript/phlex/reactive/runtime.js +175 -14
- data/lib/phlex/reactive/component/dsl.rb +5 -0
- data/lib/phlex/reactive/component/helpers.rb +79 -8
- data/lib/phlex/reactive/component/presence_declaration.rb +118 -0
- data/lib/phlex/reactive/component/registry.rb +3 -1
- data/lib/phlex/reactive/component.rb +1 -0
- data/lib/phlex/reactive/defer.rb +5 -0
- data/lib/phlex/reactive/engine.rb +9 -4
- data/lib/phlex/reactive/js.rb +24 -0
- data/lib/phlex/reactive/presence/component_methods.rb +130 -0
- data/lib/phlex/reactive/presence/roster.rb +90 -0
- data/lib/phlex/reactive/presence/transport.rb +154 -0
- data/lib/phlex/reactive/presence.rb +162 -0
- data/lib/phlex/reactive/presence_render_job.rb +26 -0
- data/lib/phlex/reactive/shortcut.rb +105 -0
- data/lib/phlex/reactive/streamable.rb +7 -3
- data/lib/phlex/reactive/version.rb +1 -1
- data/lib/phlex/reactive.rb +10 -0
- metadata +11 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Phlex
|
|
4
|
+
module Reactive
|
|
5
|
+
# A page-level keyboard shortcut (issue #357), the value behind
|
|
6
|
+
# `on(..., shortcut:)` and `on_client(:keydown, ..., shortcut:)`.
|
|
7
|
+
#
|
|
8
|
+
# Shortcut.parse("mod+z") # String form
|
|
9
|
+
# Shortcut.parse({ keys: "esc", in_editable: true }) # Hash form
|
|
10
|
+
#
|
|
11
|
+
# `mod` is meta on Apple platforms and ctrl elsewhere. Stimulus has no
|
|
12
|
+
# "either" modifier (its key filter compares all four modifier flags
|
|
13
|
+
# exactly), so a `mod` chord renders TWO descriptors, a meta twin and a ctrl
|
|
14
|
+
# twin, and the client runs only the twin for its platform. Stimulus keeps
|
|
15
|
+
# the key filtering; the client adds the platform check, the editable-target
|
|
16
|
+
# guard and preventDefault.
|
|
17
|
+
#
|
|
18
|
+
# Validation is loud, at render: Stimulus reports an unknown key token by
|
|
19
|
+
# throwing at keypress time, which is too late to notice.
|
|
20
|
+
class Shortcut
|
|
21
|
+
MODIFIERS = %w[mod meta ctrl alt shift].freeze
|
|
22
|
+
# Stimulus's default keyMappings: the named keys plus a-z and 0-9.
|
|
23
|
+
NAMED_KEYS = %w[enter tab esc space up down left right home end page_up page_down].freeze
|
|
24
|
+
HASH_KEYS = %i[keys in_editable].freeze
|
|
25
|
+
|
|
26
|
+
private_constant :MODIFIERS, :NAMED_KEYS, :HASH_KEYS
|
|
27
|
+
|
|
28
|
+
attr_reader :keys
|
|
29
|
+
|
|
30
|
+
def self.parse(spec)
|
|
31
|
+
case spec
|
|
32
|
+
when String then new(spec, in_editable: false)
|
|
33
|
+
when Hash then from_hash(spec)
|
|
34
|
+
else
|
|
35
|
+
raise ArgumentError,
|
|
36
|
+
"shortcut: takes a String (\"mod+z\") or a Hash ({ keys: \"esc\", in_editable: true }), " \
|
|
37
|
+
"got #{spec.inspect}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.from_hash(spec)
|
|
42
|
+
keys = spec[:keys]
|
|
43
|
+
in_editable = spec.fetch(:in_editable, false)
|
|
44
|
+
unless (spec.keys - HASH_KEYS).empty? && keys.is_a?(String)
|
|
45
|
+
raise ArgumentError,
|
|
46
|
+
"shortcut: as a Hash takes keys: (a String, e.g. \"esc\") and an optional " \
|
|
47
|
+
"in_editable: (true/false), got #{spec.inspect}"
|
|
48
|
+
end
|
|
49
|
+
unless [true, false].include?(in_editable)
|
|
50
|
+
raise ArgumentError, "shortcut: in_editable: is true or false, got #{in_editable.inspect}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
new(keys, in_editable:)
|
|
54
|
+
end
|
|
55
|
+
private_class_method :from_hash
|
|
56
|
+
|
|
57
|
+
def initialize(keys, in_editable:)
|
|
58
|
+
tokens = keys.downcase.split("+").map(&:strip)
|
|
59
|
+
@in_editable = in_editable
|
|
60
|
+
@keys = canonical(keys, tokens).freeze
|
|
61
|
+
freeze
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# The Stimulus key filters to bind: the meta and ctrl twins for a `mod`
|
|
65
|
+
# chord, else the chord itself.
|
|
66
|
+
def filters
|
|
67
|
+
return [@keys] unless @keys.start_with?("mod+")
|
|
68
|
+
|
|
69
|
+
%w[meta ctrl].map { @keys.sub("mod", it) }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# The wire form the client reads (data-reactive-shortcut-param, or the
|
|
73
|
+
# on_client record's "shortcut"): the canonical String, or the Hash form
|
|
74
|
+
# when the shortcut also fires inside editable targets.
|
|
75
|
+
def param = @in_editable ? { "keys" => @keys, "inEditable" => true } : @keys
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def canonical(source, tokens)
|
|
80
|
+
mods = tokens.select { MODIFIERS.include?(it) }
|
|
81
|
+
key = tokens - MODIFIERS
|
|
82
|
+
validate!(source, mods, key)
|
|
83
|
+
[*MODIFIERS.select { mods.include?(it) }, key.first].join("+")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def validate!(source, mods, key)
|
|
87
|
+
raise ArgumentError, "shortcut: #{source.inspect} must name exactly one key (e.g. \"mod+z\")" unless key.size == 1
|
|
88
|
+
raise ArgumentError, "shortcut: #{source.inspect} repeats a modifier" unless mods.uniq.size == mods.size
|
|
89
|
+
if mods.include?("mod") && mods.intersect?(%w[meta ctrl])
|
|
90
|
+
raise ArgumentError,
|
|
91
|
+
"shortcut: #{source.inspect} combines mod with meta/ctrl — mod already is meta on Apple " \
|
|
92
|
+
"platforms and ctrl elsewhere"
|
|
93
|
+
end
|
|
94
|
+
return if known_key?(key.first)
|
|
95
|
+
|
|
96
|
+
raise ArgumentError,
|
|
97
|
+
"shortcut: #{source.inspect} has an unknown key #{key.first.inspect} — use a letter, a digit, " \
|
|
98
|
+
"or one of #{NAMED_KEYS.join(", ")} (Stimulus's default key map; custom keyMappings are not " \
|
|
99
|
+
"known on the server, bind those with event: and window: instead)"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def known_key?(key) = NAMED_KEYS.include?(key) || key.match?(/\A[a-z0-9]\z/)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -37,10 +37,13 @@ module Phlex
|
|
|
37
37
|
# Focus ops steal focus in every subscriber's tab (issue #96); submit
|
|
38
38
|
# (issue #226) would force-submit every subscriber's form; paste_into
|
|
39
39
|
# (issue #228) would read every subscriber's clipboard; check_group
|
|
40
|
-
# (issue #342) would tick or clear every subscriber's selection
|
|
40
|
+
# (issue #342) would tick or clear every subscriber's selection; presence
|
|
41
|
+
# (issue #358) would write every subscriber's presence state. They
|
|
41
42
|
# belong to the actor's own reply (reply.js) or gesture (on_client / a
|
|
42
43
|
# reducer's $ops), never a broadcast. Names mirror Phlex::Reactive::JS's verbs.
|
|
43
|
-
BROADCAST_REFUSED_OPS = %w[
|
|
44
|
+
BROADCAST_REFUSED_OPS = %w[
|
|
45
|
+
focus focus_first submit paste_into persist_state persist_clear check_group presence
|
|
46
|
+
].freeze
|
|
44
47
|
|
|
45
48
|
# The broadcast_to verb kwargs (issue #185) → their Turbo stream action.
|
|
46
49
|
# SELF-TARGETING verbs derive the target from the component's #id (require a
|
|
@@ -193,7 +196,8 @@ module Phlex
|
|
|
193
196
|
"broadcast_to(js:) refuses actor-only op(s) #{refused.join(", ")} — broadcasting focus " \
|
|
194
197
|
"steals it in every subscriber's tab; broadcasting submit force-submits every " \
|
|
195
198
|
"subscriber's form; broadcasting paste_into reads every subscriber's clipboard; " \
|
|
196
|
-
"broadcasting check_group ticks or clears every subscriber's selection
|
|
199
|
+
"broadcasting check_group ticks or clears every subscriber's selection; " \
|
|
200
|
+
"broadcasting presence writes every subscriber's presence state. " \
|
|
197
201
|
"These are actor concerns (reply.js / on_client / $ops)."
|
|
198
202
|
end
|
|
199
203
|
|
data/lib/phlex/reactive.rb
CHANGED
|
@@ -66,6 +66,12 @@ module Phlex
|
|
|
66
66
|
# authorization method / mark_authorized! / declare skip_verify_authorized).
|
|
67
67
|
class AuthorizationNotVerified < Error; end
|
|
68
68
|
|
|
69
|
+
# Raised when a feature needs a transport capability the app does not have
|
|
70
|
+
# (issue #358: reactive_presence without a pgbus that keys presence by
|
|
71
|
+
# connection). Loud at first render, with the requirement in the message —
|
|
72
|
+
# there is no degraded mode to fall back to.
|
|
73
|
+
class ConfigurationError < Error; end
|
|
74
|
+
|
|
69
75
|
# Purpose string bound into every identity token's signature so a token
|
|
70
76
|
# minted for phlex-reactive can't be replayed against another verifier use.
|
|
71
77
|
IDENTITY_PURPOSE = "phlex-reactive/identity"
|
|
@@ -1283,6 +1289,10 @@ loader.do_not_eager_load("#{__dir__}/reactive/engine.rb")
|
|
|
1283
1289
|
# boot. The constant autoloads on first reference, which only happens behind
|
|
1284
1290
|
# Phlex::Reactive.defer_push_capable? (that gate requires ActiveJob::Base).
|
|
1285
1291
|
loader.do_not_eager_load("#{__dir__}/reactive/deferred_render_job.rb")
|
|
1292
|
+
# The presence roster's render job (issue #358), for the same reason: it is
|
|
1293
|
+
# only referenced behind Phlex::Reactive::Presence.capable? (which requires
|
|
1294
|
+
# ActiveJob::Base).
|
|
1295
|
+
loader.do_not_eager_load("#{__dir__}/reactive/presence_render_job.rb")
|
|
1286
1296
|
loader.setup
|
|
1287
1297
|
|
|
1288
1298
|
require "phlex/reactive/engine" if defined?(Rails::Engine)
|
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.15.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mikael Henriksson
|
|
@@ -158,6 +158,9 @@ files:
|
|
|
158
158
|
- app/javascript/phlex/reactive/features/persist.js
|
|
159
159
|
- app/javascript/phlex/reactive/features/persist.min.js
|
|
160
160
|
- app/javascript/phlex/reactive/features/persist.min.js.map
|
|
161
|
+
- app/javascript/phlex/reactive/features/presence.js
|
|
162
|
+
- app/javascript/phlex/reactive/features/presence.min.js
|
|
163
|
+
- app/javascript/phlex/reactive/features/presence.min.js.map
|
|
161
164
|
- app/javascript/phlex/reactive/features/sortable.js
|
|
162
165
|
- app/javascript/phlex/reactive/features/sortable.min.js
|
|
163
166
|
- app/javascript/phlex/reactive/features/sortable.min.js.map
|
|
@@ -195,6 +198,7 @@ files:
|
|
|
195
198
|
- lib/phlex/reactive/component/helpers.rb
|
|
196
199
|
- lib/phlex/reactive/component/identity.rb
|
|
197
200
|
- lib/phlex/reactive/component/lazy.rb
|
|
201
|
+
- lib/phlex/reactive/component/presence_declaration.rb
|
|
198
202
|
- lib/phlex/reactive/component/registry.rb
|
|
199
203
|
- lib/phlex/reactive/component/selection.rb
|
|
200
204
|
- lib/phlex/reactive/component/sortable.rb
|
|
@@ -222,10 +226,16 @@ files:
|
|
|
222
226
|
- lib/phlex/reactive/param_schema.rb
|
|
223
227
|
- lib/phlex/reactive/pending.rb
|
|
224
228
|
- lib/phlex/reactive/pending/markup.rb
|
|
229
|
+
- lib/phlex/reactive/presence.rb
|
|
230
|
+
- lib/phlex/reactive/presence/component_methods.rb
|
|
231
|
+
- lib/phlex/reactive/presence/roster.rb
|
|
232
|
+
- lib/phlex/reactive/presence/transport.rb
|
|
233
|
+
- lib/phlex/reactive/presence_render_job.rb
|
|
225
234
|
- lib/phlex/reactive/reply.rb
|
|
226
235
|
- lib/phlex/reactive/response.rb
|
|
227
236
|
- lib/phlex/reactive/settle.rb
|
|
228
237
|
- lib/phlex/reactive/settles.rb
|
|
238
|
+
- lib/phlex/reactive/shortcut.rb
|
|
229
239
|
- lib/phlex/reactive/show_conditions.rb
|
|
230
240
|
- lib/phlex/reactive/stream.rb
|
|
231
241
|
- lib/phlex/reactive/streamable.rb
|