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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4b92414ce55aa10f4ac2fb223ba0cb689928ca773750dd14c7ef80284156f255
|
|
4
|
+
data.tar.gz: bd12836456aa3f2010f8ea86c0f5c3a6a54f7dabfd04f0f5ceb28b6466fb28e6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2608d8963434dd4d8a5e015f12e17d54ba7e3c62fdbfab0dca5ffb31c8fd75786eb6467dc864143a2635fa51e1ec574f1ad45a1d804800cb553dcce0fd82f18d
|
|
7
|
+
data.tar.gz: e5ad1eeeaab37b4f69fe1ab5368fced9f427bbe56ead3ef24f63c950ded96d755d14a6f2dc9b6dd5f3a8af7fe3d96cef2eb6112eb623a5513ebba322df823cfd
|
data/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,204 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
8
8
|
|
|
9
9
|
### Fixed
|
|
10
10
|
|
|
11
|
+
- **Scoped form fields no longer silently drop their params — the #67 footgun is fixed (#184).**
|
|
12
|
+
Under `reactive_scope`, `reactive_field(:date)` now emits the scoped wire name
|
|
13
|
+
(`name="invoice[date]"`), so the POST arrives bracketed — and the endpoint unwraps
|
|
14
|
+
exactly ONE scope level before schema matching, so a FLAT schema
|
|
15
|
+
(`params: { date: :string }`) matches. Previously a scoped field either POSTed a bare
|
|
16
|
+
name (misaligned with the `reactive_show`/`reactive_compute` resolvers, which already
|
|
17
|
+
query `[name="scope[x]"]`) or forced a hand-written bracketed name + a hand-nested
|
|
18
|
+
schema that Rails' bracket-expansion silently dropped. The client's `meta.changed`
|
|
19
|
+
resolver (`#changedComputeField`) is now scope-aware too, so a scoped compute
|
|
20
|
+
component's reducer still sees the bare declared name.
|
|
21
|
+
|
|
22
|
+
- **`reactive_compute` output order no longer silently corrupts values (#183).**
|
|
23
|
+
Each output write used to dispatch an `input` event **mid-loop**, re-entering the
|
|
24
|
+
reducer synchronously from a **half-written DOM** — so a wrong `outputs:` order
|
|
25
|
+
could commit wrong values (a real money-corruption class), settling only if the
|
|
26
|
+
reducer was convergent in exactly the declared order. The client now runs the
|
|
27
|
+
reducer **once** from one pre-write input snapshot, writes all changed outputs as
|
|
28
|
+
a **batch**, paints text/mirror sinks from the settled values, then dispatches the
|
|
29
|
+
`input` events — each self-marked so the root skips re-running its own reducer
|
|
30
|
+
while other listeners (chained repaint, dirty tracking, `reactive_show`, sibling
|
|
31
|
+
roots) still fire. Declared output order is no longer semantics. Covered by a JS
|
|
32
|
+
order-independence test (declare outputs in the historically-wrong order — values
|
|
33
|
+
stay correct) and a self-re-entry-suppression test.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- **BREAKING: small sharp knives — the last 0.11 API-clarity pass (#186).**
|
|
38
|
+
Four independent edges honed, one contract frozen:
|
|
39
|
+
- **`reactive_filter` speaks fields, not selectors.** `reactive_filter(:q)` names the
|
|
40
|
+
driving FIELD and compiles it to `[name="q"]` (scope-aware, like `reactive_field`
|
|
41
|
+
from #184); `option:` defaults to `[role=option]`. The old
|
|
42
|
+
`reactive_filter(input: "#search", …)` selector form raises a guided error. The
|
|
43
|
+
client wire is byte-identical (it still receives selectors) — this is a server-only
|
|
44
|
+
compile, no client change. `group:`/`empty:` stay opt-in (emit only when passed).
|
|
45
|
+
- **`transition:` takes named legs.** `js.toggle("#x", transition: { during:, from:, to: })`
|
|
46
|
+
replaces the positional `transition: [during, from, to]` array (which raises with the
|
|
47
|
+
caller's values slotted into the named form). Compiles to the same wire array — zero
|
|
48
|
+
client change.
|
|
49
|
+
- **BREAKING: one registry reader — the plural frozen hash IS the fetch-one.** The seven
|
|
50
|
+
singular getters (`reactive_action`, `reactive_action?`, `reactive_collection_def`,
|
|
51
|
+
`reactive_collection?`, `reactive_compute_def`, `reactive_compute?`, and the bare
|
|
52
|
+
`reactive_compute(:name)` GETTER form) are removed — each raises a guided error naming
|
|
53
|
+
the hash form (`reactive_actions[:name]`, `reactive_actions.key?(:name)`,
|
|
54
|
+
`reactive_computes[:name]`, `reactive_collections[:name]`). The resolved registries now
|
|
55
|
+
come back FROZEN — they are the memoized dispatch table, so mutation raises `FrozenError`
|
|
56
|
+
and can never corrupt default-deny. The `reactive_compute :name, inputs:, outputs:`
|
|
57
|
+
SETTER is unchanged.
|
|
58
|
+
- **`reply.append`/`reply.prepend` accept row kwargs (non-breaking).** Extra kwargs
|
|
59
|
+
(`reply.append(item, to: :items, autofocus: true)`) now thread through to the row
|
|
60
|
+
component's initializer (`ItemRow.new(item:, autofocus: true)`). Additive — no-kwarg
|
|
61
|
+
calls are unchanged.
|
|
62
|
+
- **`defer.phlex_reactive` joins the frozen instrumentation contract (non-breaking).**
|
|
63
|
+
The deferred-render endpoint's `ActiveSupport::Notifications` event now has a
|
|
64
|
+
request-spec contract test driving all five outcomes (`ok`/`no_content`/`invalid_token`/
|
|
65
|
+
`not_found`/`unauthorized`), freezing its `{ component:, outcome: }` payload shape (a
|
|
66
|
+
rename fails CI, like `action`/`render`/`broadcast`). Documented in the README
|
|
67
|
+
instrumentation table and the observability section of the performance docs page.
|
|
68
|
+
|
|
69
|
+
- **BREAKING: one `broadcast_to` — verbs as kwargs, components as payloads (#185).**
|
|
70
|
+
The 11 `broadcast_*_to` / `broadcast_*_to_each` methods collapse into ONE
|
|
71
|
+
`broadcast_to` where the verb is a kwarg and its value is the payload:
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
# before # after
|
|
75
|
+
Item.broadcast_replace_to(@list, :todos, Item.broadcast_to(@list, :todos,
|
|
76
|
+
model: @todo, morph: true) replace: @todo, morph: true)
|
|
77
|
+
Row.broadcast_append_to(@list, target: t, Row.broadcast_to(@list, append: item, target: t)
|
|
78
|
+
model: item)
|
|
79
|
+
Counter.broadcast_replace_to_each(keys, Counter.broadcast_to(each: keys, replace: counter)
|
|
80
|
+
model: counter)
|
|
81
|
+
Badge.broadcast_js_to(user, :alerts, ops) Badge.broadcast_to(user, :alerts, js: ops)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
A Hash payload is the component's init kwargs verbatim (`update: { room:, author: }`),
|
|
85
|
+
killing the old `**options` collision (a component with an init kwarg named
|
|
86
|
+
`target`/`morph`/`exclude` is broadcastable again). Payloads can be BUILT
|
|
87
|
+
components, and the new module-level `Phlex::Reactive.broadcast_to(@list, :todos,
|
|
88
|
+
update: TodoCount.new(...), target: "todos-count")` broadcasts a NON-Streamable
|
|
89
|
+
component (a count badge) — instrumented — without hand-rolling the raw channel +
|
|
90
|
+
render. Self-targeting verbs (`replace:`/`remove:`) require a Streamable payload
|
|
91
|
+
(its `#id` is the target); container verbs (`update:`/`append:`/`prepend:`) take any
|
|
92
|
+
component. Every removed method raises a guided error printing the `broadcast_to`
|
|
93
|
+
rewrite. `exclude:`/`visible_to:` still thread to pgbus through the capability-gated
|
|
94
|
+
thread-local path — unchanged (verified against pgbus 0.11.0).
|
|
95
|
+
|
|
96
|
+
- **BREAKING: `to_stream_morph` is removed — morph is a kwarg (#185).**
|
|
97
|
+
Use `to_stream_replace(morph: true)` (byte-identical wire). `reply.morph` /
|
|
98
|
+
`reply.replace(morph: true)` are unchanged.
|
|
99
|
+
|
|
100
|
+
- **BREAKING: forms & fields — scope everywhere, one dirty declaration, named schemas (#184).**
|
|
101
|
+
- **`reactive_scope` extends to `reactive_field` and the param unwrap:** a scoped
|
|
102
|
+
field emits `name="scope[field]"` and the endpoint peels one scope level, so the
|
|
103
|
+
schema stays flat. A schema nested under the scope key raises a guided
|
|
104
|
+
`ArgumentError` from BOTH the `action` macro and `reactive_scope` (either
|
|
105
|
+
declaration order).
|
|
106
|
+
- **One dirty declaration — `reactive_dirty`:** `reactive_dirty warn_unsaved: true`
|
|
107
|
+
(class-level) and `reactive_dirty only: %i[title]` replace
|
|
108
|
+
`reactive_root(track_dirty:, warn_unsaved:)` + `reactive_field(dirty:)`. The
|
|
109
|
+
removed kwargs raise guided errors; the emitted DOM is unchanged (zero client
|
|
110
|
+
change).
|
|
111
|
+
- **Named param schemas — `Phlex::Reactive.param_schema`:** register a reusable
|
|
112
|
+
schema in an initializer (`param_schema :todo, title: :string, …`) and resolve it
|
|
113
|
+
with `action :save, params: :todo`, or compose with `{ **param_schema(:todo), … }`.
|
|
114
|
+
Frozen after boot (the `param_type` precedent); an unknown name lists the
|
|
115
|
+
registered ones. `params:` still takes a Hash.
|
|
116
|
+
- **One binding helper:** `reactive_input` / `reactive_select` are removed — use
|
|
117
|
+
`input(**reactive_field(…))` / `select(**reactive_field(…)) { … }` /
|
|
118
|
+
`textarea(**reactive_field(…))`. The stubs raise the rewrite.
|
|
119
|
+
|
|
120
|
+
- **BREAKING: `reactive_compute` is scope-aware, root-bound, and permit-shaped (#183).**
|
|
121
|
+
- **Bind + listen at the root:** `reactive_root(compute: :name)` emits the compute
|
|
122
|
+
descriptors AND the `input->reactive#recompute` delegation, so fields carry
|
|
123
|
+
ZERO per-field wiring. Conditional binding collapses to one expression
|
|
124
|
+
(`reactive_root(compute: (:split unless @order.persisted?))`; `nil` = no binding).
|
|
125
|
+
`reactive_compute_attrs(...)` raises a guided error naming the new form.
|
|
126
|
+
- **Scope-resolved names:** under `reactive_scope :order`, a bare compute input/
|
|
127
|
+
output `cash` resolves as `[name="order[cash]"]` client-side (the same
|
|
128
|
+
convention `reactive_show`/`reactive_field` use); a bracketed literal passes
|
|
129
|
+
through unscoped.
|
|
130
|
+
- **Permit-style inputs:** `inputs: [:qty, title: :string]` — bare symbols default
|
|
131
|
+
to `:number`, a trailing Hash types the exceptions. The old array form
|
|
132
|
+
(`%i[a b]`) and hash form (`{ a: :number }`) are degenerate cases (zero shim,
|
|
133
|
+
byte-identical wire).
|
|
134
|
+
- **Sinks declare themselves:** every reducer result key paints into any matching
|
|
135
|
+
sink — an owned field iff in `outputs:` (the allowlist), any owned
|
|
136
|
+
`reactive_text` node by presence, any declared `mirror:` id. An `outputs:` entry
|
|
137
|
+
that existed only to reach a text node is now redundant (old declarations keep
|
|
138
|
+
working). `reactive_text(:name)` with no explicit initial seeds its first paint
|
|
139
|
+
from `reactive_values` when covered.
|
|
140
|
+
|
|
141
|
+
- **BREAKING: `reply` is the only door — the `Response` class verbs are removed (#182).**
|
|
142
|
+
`Phlex::Reactive::Response.replace(self)` / `.morph` / `.update` / `.remove` /
|
|
143
|
+
`.redirect` / `.with` / `.streams` (and the collection class methods) are removed
|
|
144
|
+
as public entry points — each raises a guided `ArgumentError` naming the
|
|
145
|
+
`reply.<verb>` rewrite. Actions return `reply.<verb>` (the subject-bound door added
|
|
146
|
+
earlier); the immutable `Response` value object and its instance chain
|
|
147
|
+
(`.flash`/`.stream`/`.also`/`.js`/`.defer`) are unchanged, so the endpoint reads it
|
|
148
|
+
exactly as before. One concept, one entry point.
|
|
149
|
+
|
|
150
|
+
- **BREAKING: reactive-collection replies read as Ruby — `to:`/`from:` keywords (#182).**
|
|
151
|
+
The collection name moved from a leading Symbol to a keyword, and the `UNSET`
|
|
152
|
+
sentinel that overloaded `reply.remove` is gone (dispatch keys on `from:`'s
|
|
153
|
+
presence):
|
|
154
|
+
|
|
155
|
+
| Before (removed) | After |
|
|
156
|
+
|---|---|
|
|
157
|
+
| `reply.append(:items, item)` | `reply.append(item, to: :items)` |
|
|
158
|
+
| `reply.prepend(:items, item)` | `reply.prepend(item, to: :items)` |
|
|
159
|
+
| `reply.remove(:items, id)` | `reply.remove(id, from: :items)` |
|
|
160
|
+
| `reply.remove` (bare) | `reply.remove` — unchanged (removes self) |
|
|
161
|
+
|
|
162
|
+
A Symbol in the model position (the old shape) raises a guided rewrite.
|
|
163
|
+
|
|
164
|
+
- **BREAKING: one flash contract — `flash_component` is a callable (#182).**
|
|
165
|
+
`Phlex::Reactive.flash_component` is now a lambda the app owns, so the gem no
|
|
166
|
+
longer guesses your component's kwargs (the old hardcoded `new(level:, content:)`
|
|
167
|
+
collided with real flash components):
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
# before: Phlex::Reactive.flash_component = MyFlash (gem calls MyFlash.new(level:, content:))
|
|
171
|
+
# after:
|
|
172
|
+
Phlex::Reactive.flash_component = ->(level, content) { MyFlash.new(level:, message: content) }
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Assigning a Class raises a guided error printing the one-line lambda. The internal
|
|
176
|
+
flash builders (`flash_stream`/`flash_html`/`default_flash_html`) are now private.
|
|
177
|
+
|
|
178
|
+
- **BREAKING: `also_update` / `also_replace` collapse into one `.also` (#182).**
|
|
179
|
+
The lying `html:` kwarg (it accepted components too) and the per-companion method
|
|
180
|
+
choice are gone:
|
|
181
|
+
|
|
182
|
+
```ruby
|
|
183
|
+
# before
|
|
184
|
+
reply.replace.also_update("page_heading", html: @account.name)
|
|
185
|
+
reply.replace.also_replace(SummaryCard.new(account: @account), morph: true)
|
|
186
|
+
# after
|
|
187
|
+
reply.replace.also(page_heading: @account.name) # target => content
|
|
188
|
+
reply.replace.also(SummaryCard.new(account: @account), morph: true) # a component at its own id
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
String content is HTML-escaped, component content rendered — the escaping contract
|
|
192
|
+
is verbatim. `also_update`/`also_replace` raise guided rewrites.
|
|
193
|
+
|
|
194
|
+
- **BREAKING: the `flash_builder` / `reset_flash_builder!` aliases are removed (#182).**
|
|
195
|
+
They were "permanent aliases" for `stream_builder` / `reset_stream_builder!` that
|
|
196
|
+
contradicted the clean-break rule. Each old name now raises a guided `NoMethodError`
|
|
197
|
+
naming the real method.
|
|
198
|
+
|
|
199
|
+
### Fixed
|
|
200
|
+
|
|
201
|
+
- **A pending-state text swap no longer destroys a composite trigger's icon (#181).**
|
|
202
|
+
The former `disable_with:`/`loading:` text swap wrote `trigger.textContent`, which
|
|
203
|
+
**flattens every child node** — a `<button><svg/> Save</button>` lost its icon the
|
|
204
|
+
instant the request enqueued, and it never came back on restore. `busy:`'s swap now
|
|
205
|
+
reads and restores `innerHTML`, so an icon-plus-label trigger round-trips intact.
|
|
206
|
+
Covered by a new composite-trigger browser assertion and a JS child-preservation
|
|
207
|
+
unit test.
|
|
208
|
+
|
|
11
209
|
- **`exclude:`/`visible_to:` now actually reach pgbus — actor-echo suppression works over SSE (#187).**
|
|
12
210
|
`broadcast_*_to`/`broadcast_*_to_each` passed `exclude:`/`visible_to:` as keyword
|
|
13
211
|
arguments to `Turbo::StreamsChannel.broadcast_*_to`, but turbo-rails swallows
|
|
@@ -21,6 +219,32 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
21
219
|
|
|
22
220
|
### Added
|
|
23
221
|
|
|
222
|
+
- **Conditional confirm — warn only when the values look suspect (#179).**
|
|
223
|
+
`confirm:` now takes a Hash for soft-validation-before-submit, so the dialog fires
|
|
224
|
+
ONLY when the field values are wrong — instead of a hand-written submit handler that
|
|
225
|
+
inspects fields and calls `confirm()` itself. Two forms, both evaluated client-side
|
|
226
|
+
over the same collected fields `reactive_compute` reads:
|
|
227
|
+
- **Declarative** — `confirm: { when: { total: 0 }, message: "Total is 0 — continue?" }`.
|
|
228
|
+
`when:` reuses `reactive_show`'s conditions language verbatim: a scalar is equals,
|
|
229
|
+
a `Range` is a threshold (`qty: 100..`), an `Array` is membership. Zero JS. The
|
|
230
|
+
dialog fires when the condition MATCHES; a clean value submits silently.
|
|
231
|
+
- **Named predicate** — `confirm: { predicate: "end_before_start", message: "…" }` for
|
|
232
|
+
multi-field logic the single-field form can't express. Register a pure function at
|
|
233
|
+
boot (`setConfirmPredicate("end_before_start", ({ starts_at, ends_at }) => ends_at < starts_at)`),
|
|
234
|
+
the twin of `setComputeReducer`. An unregistered name warns and proceeds without a
|
|
235
|
+
dialog. Works on `on(...)` AND `on_client(...)`. The predicate is soft-validation UX,
|
|
236
|
+
**not authorization** — a user can bypass it and the action still hits the endpoint's
|
|
237
|
+
real authorize/default-deny; never let it stand in for a server-side check.
|
|
238
|
+
|
|
239
|
+
- **`confirm:` on `on_client(...)` — themed confirmation for zero-round-trip client ops (#178).**
|
|
240
|
+
The client-op path gains the SAME overridable `confirmResolver` gate `on(:action, confirm:)`
|
|
241
|
+
has (#52/#55). A destructive-feeling client op (clear a draft, reset a form) gets the
|
|
242
|
+
app's themed dialog with one line and no round trip:
|
|
243
|
+
`button(**on_client(:click, js.text("#draft", ""), confirm: "Discard this draft?"))`.
|
|
244
|
+
The gate lives in the user-gesture path (`runOps`), never in the shared op applier — a
|
|
245
|
+
server-pushed `reactive:js` op stream must not prompt. `setConfirmResolver` now themes
|
|
246
|
+
both paths at once.
|
|
247
|
+
|
|
24
248
|
- **CI transport verification matrix — the browser suite runs on Action Cable AND pgbus (#187).**
|
|
25
249
|
The system job is now `server × transport` (Puma/Falcon × cable/pgbus). The pgbus
|
|
26
250
|
cells add a plain `postgres:18` (pgbus vendors the PGMQ schema via its own
|
|
@@ -34,6 +258,28 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
34
258
|
|
|
35
259
|
### Changed
|
|
36
260
|
|
|
261
|
+
- **BREAKING: ONE pending-state vocabulary — `busy:` replaces `loading:` / `disable_with:` (#181).**
|
|
262
|
+
A trigger's declarative pending affordance is now a single `on(…, busy:)` kwarg
|
|
263
|
+
that shares `optimistic:`'s key vocabulary and normalizer — the only difference
|
|
264
|
+
is the lifecycle (`busy:` reverts on **settle**, `optimistic:` on **failure**).
|
|
265
|
+
`busy:` takes a **String shorthand** (`busy: "Saving…"` ≡ `{ disable: true, text:
|
|
266
|
+
"Saving…" }`) or a **Hash** with the same keys as `optimistic:`
|
|
267
|
+
(`add_class:`/`remove_class:`/`toggle_class:`/`hide:`/`show:`/`disable:`/`text:`/`to:`).
|
|
268
|
+
The removed `loading:` (with its odd `class:` key), `disable_with:`, and the
|
|
269
|
+
`on(…, listnav:)` kwarg each raise a **guided `ArgumentError`** printing the exact
|
|
270
|
+
rewrite. Combobox keyboard nav is now the standalone `reactive_listnav` (composed
|
|
271
|
+
via `mix`), which defaults its option selector to `[role=option]`. Migration:
|
|
272
|
+
|
|
273
|
+
| Before (removed) | After |
|
|
274
|
+
|---|---|
|
|
275
|
+
| `on(:save, disable_with: "Saving…")` | `on(:save, busy: "Saving…")` |
|
|
276
|
+
| `on(:save, loading: { disable: true, class: "opacity-50", text: "Saving…" })` | `on(:save, busy: { disable: true, add_class: "opacity-50", text: "Saving…" })` |
|
|
277
|
+
| `on(:search, event: "input", listnav: "[role=option]")` | `mix(on(:search, event: "input"), reactive_listnav)` |
|
|
278
|
+
|
|
279
|
+
The wire attribute is renamed `data-reactive-loading-param` → `data-reactive-busy-param`;
|
|
280
|
+
the client keeps a **read shim** for the old attribute for one minor so an
|
|
281
|
+
in-flight page rendered by the previous gem across a deploy keeps its affordance.
|
|
282
|
+
|
|
37
283
|
- **BREAKING: `reactive_show` speaks ONE conditions language — `if:`/`if_any:`/`unless:` (#180).**
|
|
38
284
|
The four accreted dialects (the positional-field predicate kwargs
|
|
39
285
|
`equals:`/`not:`/`in:`/`gte:`/`gt:`/`lte:`/`lt:`, the `all:`/`any:` term
|