phlex-reactive 0.11.1 → 0.11.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 +49 -0
- data/app/javascript/phlex/reactive/reactive_controller.js +114 -7
- 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/test_helpers/system.rb +193 -0
- data/lib/phlex/reactive/test_helpers.rb +6 -0
- data/lib/phlex/reactive/version.rb +1 -1
- data/lib/phlex/reactive.rb +5 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 23b25b41281afc59503c63e9e4fbcee696d1265b96682c1c97bfd0adf385055d
|
|
4
|
+
data.tar.gz: 7204885d357720c85e57c8b44192d6e000f91c985346177a7ddcefc28f7451cc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2afb0dfef67df5cc60f176db516ca7c4ed5553e7f24a9df9ff098dc7a897e1085594c698661d437f8ea336b4eab5d6bc76b884073f0f52b37b09a8f5fe4bd711
|
|
7
|
+
data.tar.gz: 411a32490ecdc9224d18e5db37b443286d1d3e47210e733e2cb9e6083aac5af2ac5ead6f96e54ab1cf0629483addd3f888de209976a6b2161259c5a6d9c0a93e
|
data/CHANGELOG.md
CHANGED
|
@@ -6,8 +6,57 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **A global reactive-activity signal — the analogue of Turbo's progress bar (#201).**
|
|
12
|
+
The client now maintains a document-level count of in-flight reactive operations
|
|
13
|
+
across ALL roots — incremented when a dispatch round trip or a deferred render
|
|
14
|
+
starts, decremented when it settles — and exposes it two ways:
|
|
15
|
+
- a marker on `<html>`: `data-reactive-active` is present while the count is `> 0`,
|
|
16
|
+
so CSS can drive a global spinner and code (or a test) can read "is the reactive
|
|
17
|
+
layer settling?" without knowing about any individual root;
|
|
18
|
+
- events on `document`: `reactive:busy` fires on the `0 → >0` edge and
|
|
19
|
+
`reactive:idle` on the `>0 → 0` edge (edges only, each carrying `{ count }`), so
|
|
20
|
+
an app can flip a spinner or gate an "unsaved changes" guard globally.
|
|
21
|
+
|
|
22
|
+
Purely additive — every per-root marker/event (`data-reactive-busy`, `aria-busy`,
|
|
23
|
+
`data-reactive-defer-pending`) is unchanged. A distinct attribute name
|
|
24
|
+
(`data-reactive-active`, not the per-root `data-reactive-busy`) keeps the
|
|
25
|
+
document-level signal unambiguous. `compute_seed` is not counted: `recompute()` is
|
|
26
|
+
synchronous, so a seed settles inline with no async window to await.
|
|
27
|
+
|
|
28
|
+
- **`Phlex::Reactive::TestHelpers::System` — system/browser test helpers built on the
|
|
29
|
+
activity signal (#201).** Opt-in and loaded only when Capybara is present (the same
|
|
30
|
+
optional-require gate as the RSpec matchers), so it stays entirely out of the
|
|
31
|
+
runtime path. Mix it into system examples:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
RSpec.configure do |c|
|
|
35
|
+
c.include Phlex::Reactive::TestHelpers::System, type: :system
|
|
36
|
+
end
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
- `wait_for_reactive` — block until the layer is idle (every round trip AND deferred
|
|
40
|
+
render settled, the `<html>` marker cleared). The system-test twin of
|
|
41
|
+
`wait_for_turbo`, which watches the Turbo progress bar, not a reactive morph/seed.
|
|
42
|
+
- `have_reactive_value(id, value)` / `have_reactive_text(id, value)` — assert a field
|
|
43
|
+
or a mirror/recap node by RE-RESOLVING it by id every poll, so a `reactive_compute`
|
|
44
|
+
re-seed or a morph that replaces the node after the action returns can never raise
|
|
45
|
+
`StaleReferenceError` or read a transient blank — the matcher waits for the value
|
|
46
|
+
to settle.
|
|
47
|
+
|
|
9
48
|
### Fixed
|
|
10
49
|
|
|
50
|
+
- **`have_reactive_value` now reads the field's `.value` property, so it can verify a
|
|
51
|
+
reducer-set disabled/computed field (#204).** The matcher (added in #201) delegated to
|
|
52
|
+
Capybara's `have_field(with:)`, which matches the value **attribute** — but a
|
|
53
|
+
`reactive_compute` reducer paints a computed output by setting its JS `.value`
|
|
54
|
+
**property** (`el.value = …`), and for a **disabled / read-only** output the attribute
|
|
55
|
+
never reflects that. So the matcher read `""` and failed on exactly the fields it was
|
|
56
|
+
built for. It now re-resolves the field by id **or name** each poll and asserts the live
|
|
57
|
+
`.value` property (via `evaluate_script`), covering enabled AND disabled/computed fields
|
|
58
|
+
while keeping the morph-immunity. `have_reactive_text` (textContent) was unaffected.
|
|
59
|
+
|
|
11
60
|
- **A freshly-rendered `reactive_compute` root now self-seeds its derived fields on connect (#199).**
|
|
12
61
|
Before, a compute root computed nothing until the first user `input` — a fresh render
|
|
13
62
|
(or a server validation-error re-render that replaced the body) left every derived
|
|
@@ -125,9 +125,28 @@ export function registerReactiveJs() {
|
|
|
125
125
|
// keystrokes can never paint stale totals over fresh ones.
|
|
126
126
|
const pendingDefers = new Map()
|
|
127
127
|
|
|
128
|
+
// Register/drop a target's pending defer entry, keeping the GLOBAL activity
|
|
129
|
+
// counter (issue #201) balanced against the Map's ACTUAL key presence — a defer
|
|
130
|
+
// is one in-flight reactive operation for as long as its registry entry lives.
|
|
131
|
+
// Keying enter/exit on the presence TRANSITION (not the raw call) means a
|
|
132
|
+
// re-set of an already-present key, or a delete of an absent one, can never
|
|
133
|
+
// unbalance the count. Both the fetch (pull) and stream (push) lane route their
|
|
134
|
+
// registry mutations through here, so the push lane is counted correctly even
|
|
135
|
+
// though its DOM pending markers clear by a node swap, not clearDeferPending.
|
|
136
|
+
function setPendingDefer(targetId, entry) {
|
|
137
|
+
const isNew = !pendingDefers.has(targetId)
|
|
138
|
+
pendingDefers.set(targetId, entry)
|
|
139
|
+
if (isNew) enterReactiveActivity()
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function deletePendingDefer(targetId) {
|
|
143
|
+
if (pendingDefers.delete(targetId)) exitReactiveActivity()
|
|
144
|
+
}
|
|
145
|
+
|
|
128
146
|
// Test seam: clear the module-level registry between unit tests. Also resets
|
|
129
147
|
// the one-shot settle-listener guard so a test's fresh document re-registers
|
|
130
|
-
// the turbo:before-stream-render settler.
|
|
148
|
+
// the turbo:before-stream-render settler. Does NOT touch the activity counter —
|
|
149
|
+
// resetReactiveActivity is its own seam (the two are reset together in tests).
|
|
131
150
|
export function resetReactiveDefers() {
|
|
132
151
|
pendingDefers.clear()
|
|
133
152
|
deferStreamSettleRegistered = false
|
|
@@ -184,7 +203,7 @@ function settleStreamDeferOnRender(event) {
|
|
|
184
203
|
? target.slice("reactive-defer-src-".length)
|
|
185
204
|
: target
|
|
186
205
|
const entry = pendingDefers.get(targetId)
|
|
187
|
-
if (entry?.via === "stream")
|
|
206
|
+
if (entry?.via === "stream") deletePendingDefer(targetId)
|
|
188
207
|
}
|
|
189
208
|
|
|
190
209
|
// The pull lane: mark the target pending and POST the signed defer token to
|
|
@@ -198,7 +217,7 @@ function startFetchDefer(targetId, token) {
|
|
|
198
217
|
supersedeDefer(targetId)
|
|
199
218
|
markDeferPending(el)
|
|
200
219
|
const entry = { via: "fetch", abort: new AbortController(), timedOut: false }
|
|
201
|
-
|
|
220
|
+
setPendingDefer(targetId, entry)
|
|
202
221
|
performDeferFetch(targetId, entry, token)
|
|
203
222
|
}
|
|
204
223
|
|
|
@@ -248,7 +267,7 @@ function startStreamDefer(targetId, directive) {
|
|
|
248
267
|
// re-finds the element by id instead. The entry is dropped on
|
|
249
268
|
// supersession or when the arriving broadcast replaces the target (a
|
|
250
269
|
// turbo:before-stream-render hook, below).
|
|
251
|
-
|
|
270
|
+
setPendingDefer(targetId, { via: "stream" })
|
|
252
271
|
}
|
|
253
272
|
|
|
254
273
|
// The deterministic id of a target's one-shot <pgbus-stream-source>.
|
|
@@ -332,7 +351,7 @@ async function performDeferFetch(targetId, entry, token) {
|
|
|
332
351
|
function supersedeDefer(targetId) {
|
|
333
352
|
const existing = pendingDefers.get(targetId)
|
|
334
353
|
if (!existing) return
|
|
335
|
-
|
|
354
|
+
deletePendingDefer(targetId)
|
|
336
355
|
if (existing.via === "fetch") existing.abort.abort()
|
|
337
356
|
// Stream lane: re-find the old source by its deterministic id and remove it
|
|
338
357
|
// (unsubscribe) — we deliberately don't hold a strong ref to the detached
|
|
@@ -353,7 +372,7 @@ function clearDeferPending(el) {
|
|
|
353
372
|
// Success/204: drop the registry entry, clear pending, and clear any prior
|
|
354
373
|
// defer failure marker (recovery resets error-driven CSS, issue #100 style).
|
|
355
374
|
function settleDefer(targetId) {
|
|
356
|
-
|
|
375
|
+
deletePendingDefer(targetId)
|
|
357
376
|
const el = document.getElementById(targetId)
|
|
358
377
|
if (!el) return
|
|
359
378
|
clearDeferPending(el)
|
|
@@ -365,7 +384,7 @@ function settleDefer(targetId) {
|
|
|
365
384
|
// reactive:error whose retry() re-enters the defer fetch with the SAME token
|
|
366
385
|
// (still valid inside the TTL; an expired token 400s into this same path).
|
|
367
386
|
function failDefer(targetId, token, status) {
|
|
368
|
-
|
|
387
|
+
deletePendingDefer(targetId)
|
|
369
388
|
const el = document.getElementById(targetId)
|
|
370
389
|
if (!el) return
|
|
371
390
|
clearDeferPending(el)
|
|
@@ -560,6 +579,87 @@ export function __resetReactiveLatencyForTest() {
|
|
|
560
579
|
latencyBannerShown = false
|
|
561
580
|
}
|
|
562
581
|
|
|
582
|
+
// --- Global reactive-activity signal (issue #201) --------------------------
|
|
583
|
+
// A DOCUMENT-LEVEL count of in-flight reactive operations — the direct analogue
|
|
584
|
+
// of Turbo's progress bar, but for reactive round trips and deferred renders
|
|
585
|
+
// instead of navigations. Anything that starts an async reactive operation calls
|
|
586
|
+
// enterReactiveActivity(); when it settles (success OR failure, on every path) it
|
|
587
|
+
// calls exitReactiveActivity(). The count is exposed two ways so an app — or a
|
|
588
|
+
// system test — can key off "is the reactive layer settling?" without knowing
|
|
589
|
+
// about any individual root:
|
|
590
|
+
//
|
|
591
|
+
// * a marker on <html>: data-reactive-active present while count > 0 (CSS can
|
|
592
|
+
// drive a global spinner; code/tests can read it). A DISTINCT name from the
|
|
593
|
+
// per-root data-reactive-busy so a [data-reactive-busy] selector never also
|
|
594
|
+
// matches the document element.
|
|
595
|
+
// * events on document: reactive:busy on the 0 -> >0 edge, reactive:idle on the
|
|
596
|
+
// >0 -> 0 edge — EDGES ONLY (not once per op), each carrying { count }.
|
|
597
|
+
//
|
|
598
|
+
// It sums ACROSS all reactive roots (module-level, not per-controller) and across
|
|
599
|
+
// the two async lifecycles wired below:
|
|
600
|
+
// * dispatch — entered in #applyBusy (at ENQUEUE, so the queue wait counts too),
|
|
601
|
+
// exited in the settle closure #perform runs in its finally.
|
|
602
|
+
// * defer — entered/exited with the pendingDefers registry (set/delete), the
|
|
603
|
+
// ONE registry both the fetch (pull) and the stream (push) lane maintain — so
|
|
604
|
+
// the push lane stays balanced even though it clears its pending markers by a
|
|
605
|
+
// node swap, not clearDeferPending. A supersede is delete-then-set (net zero),
|
|
606
|
+
// which is correct: a fast typist's replaced defer is still "layer busy".
|
|
607
|
+
//
|
|
608
|
+
// compute-seed is deliberately NOT counted: recompute() is synchronous, so a seed
|
|
609
|
+
// is fully applied by the time the call returns — there is no async window to await
|
|
610
|
+
// (the "value settles a beat after a morph/seed" case the issue describes is
|
|
611
|
+
// covered by the System test helpers' re-resolve-by-id polling, not this counter).
|
|
612
|
+
export const ACTIVE_ATTR = "data-reactive-active"
|
|
613
|
+
|
|
614
|
+
let activityCount = 0
|
|
615
|
+
|
|
616
|
+
// Increment the global in-flight count; on the 0 -> 1 edge, mark <html> and fire
|
|
617
|
+
// reactive:busy. Fully defensive — a non-browser/test document with no
|
|
618
|
+
// documentElement/dispatchEvent still tracks the count and simply skips the DOM
|
|
619
|
+
// side effects (a global signal must never throw during bootstrap or a round trip).
|
|
620
|
+
export function enterReactiveActivity() {
|
|
621
|
+
activityCount++
|
|
622
|
+
if (activityCount === 1) syncReactiveActivity("reactive:busy")
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// Decrement the global in-flight count, clamped at 0 so an unbalanced exit can
|
|
626
|
+
// never drive it negative (which would wedge the marker on forever). On the
|
|
627
|
+
// 1 -> 0 edge, clear the <html> marker and fire reactive:idle.
|
|
628
|
+
export function exitReactiveActivity() {
|
|
629
|
+
if (activityCount === 0) return
|
|
630
|
+
activityCount--
|
|
631
|
+
if (activityCount === 0) syncReactiveActivity("reactive:idle")
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// The current in-flight count — a test seam and a runtime read (an app can gate an
|
|
635
|
+
// "unsaved changes" prompt on `reactiveActivityCount() > 0`).
|
|
636
|
+
export function reactiveActivityCount() {
|
|
637
|
+
return activityCount
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Test seam: reset the module-level counter (and clear the marker) between tests,
|
|
641
|
+
// since the module is imported once per bun run.
|
|
642
|
+
export function resetReactiveActivity() {
|
|
643
|
+
activityCount = 0
|
|
644
|
+
const root = typeof document !== "undefined" ? document.documentElement : null
|
|
645
|
+
root?.removeAttribute?.(ACTIVE_ATTR)
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Write the <html> marker from the current count and fire the edge event on
|
|
649
|
+
// document. Both sides are independently guarded so a partial document stub (a
|
|
650
|
+
// documentElement without toggleAttribute, or a document without dispatchEvent)
|
|
651
|
+
// degrades to a no-op rather than throwing.
|
|
652
|
+
function syncReactiveActivity(eventName) {
|
|
653
|
+
if (typeof document === "undefined") return
|
|
654
|
+
const root = document.documentElement
|
|
655
|
+
if (typeof root?.toggleAttribute === "function") {
|
|
656
|
+
root.toggleAttribute(ACTIVE_ATTR, activityCount > 0)
|
|
657
|
+
}
|
|
658
|
+
if (typeof document.dispatchEvent === "function" && typeof CustomEvent === "function") {
|
|
659
|
+
document.dispatchEvent(new CustomEvent(eventName, { detail: { count: activityCount } }))
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
563
663
|
export function registerReactiveActions() {
|
|
564
664
|
registerReactiveVisit()
|
|
565
665
|
registerReactiveToken()
|
|
@@ -2993,12 +3093,19 @@ export default class extends Controller {
|
|
|
2993
3093
|
#applyBusy(action, trigger, busy) {
|
|
2994
3094
|
this.#markBusy(action, trigger)
|
|
2995
3095
|
const undo = busy ? this.#applyHint(busy, trigger, false) : []
|
|
3096
|
+
// Global activity signal (issue #201): this enqueue is one in-flight reactive
|
|
3097
|
+
// operation. Entered HERE (at enqueue, via #applyBusy) so the document marker
|
|
3098
|
+
// covers the whole pending window — queue wait included — exactly like the
|
|
3099
|
+
// per-root busy vocabulary. Exited once in the settle closure below, which
|
|
3100
|
+
// #perform runs in its finally on EVERY exit path (success or any failure).
|
|
3101
|
+
enterReactiveActivity()
|
|
2996
3102
|
|
|
2997
3103
|
let settled = false
|
|
2998
3104
|
return () => {
|
|
2999
3105
|
if (settled) return // one settle per enqueue, even if called twice
|
|
3000
3106
|
settled = true
|
|
3001
3107
|
this.#unmarkBusy(action, trigger)
|
|
3108
|
+
exitReactiveActivity()
|
|
3002
3109
|
// Busy reverts on SETTLE regardless of outcome, guarded per element.
|
|
3003
3110
|
for (const op of undo) op()
|
|
3004
3111
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{Controller as i}from"@hotwired/stimulus";import{confirmResolver as k}from"phlex/reactive/confirm";import{computeReducer as n}from"phlex/reactive/compute";import{confirmPredicate as a}from"phlex/reactive/confirm_predicate";function r(){let j=window.Turbo?.StreamActions;if(!j||j["reactive:visit"])return;j["reactive:visit"]=function(){let Q=this.getAttribute("data-url");if(Q)window.Turbo.visit(Q,{action:"advance"})}}function t(){let j=window.Turbo?.StreamActions;if(!j||j["reactive:token"])return;j["reactive:token"]=function(){let Q=this.getAttribute("data-reactive-token-value"),X=this.getAttribute("target");if(!Q||!X)return;let Z=document.getElementById(X);if(Z)Z.setAttribute("data-reactive-token-value",Q)}}function e(){let j=window.Turbo?.StreamActions;if(!j||j["reactive:js"])return;j["reactive:js"]=function(){let Q=o(this.getAttribute("data-reactive-ops"));if(!Q.length)return;let X=this.getAttribute("target"),Z=X?document.getElementById(X):null;if(X&&!Z)return;s(Q,($)=>Fj($,Z))}}var N=new Map;function Sj(){N.clear(),R=!1}function kj(j){return N.get(j)?.via}var R=!1;function jj(){let j=window.Turbo?.StreamActions;if(!j||j["reactive:defer"])return;if(j["reactive:defer"]=function(){let Q=this.getAttribute("target");if(!Q)return;if(this.getAttribute("data-reactive-defer-via")==="stream"){Xj(Q,this);return}let X=this.getAttribute("data-reactive-defer-token");if(!X)return;B(Q,X)},!R&&typeof document<"u"&&document.addEventListener)R=!0,document.addEventListener("turbo:before-stream-render",Qj)}function Qj(j){let X=j.target?.getAttribute?.("target");if(!X)return;let Z=X.startsWith("reactive-defer-src-")?X.slice(19):X;if(N.get(Z)?.via==="stream")N.delete(Z)}function B(j,Q){let X=document.getElementById(j);if(!X){console.warn(`[phlex-reactive] reactive:defer target #${j} is not on the page — skipped`);return}f(j),p(X);let Z={via:"fetch",abort:new AbortController,timedOut:!1};N.set(j,Z),Zj(j,Z,Q)}function Xj(j,Q){let X=document.getElementById(j);if(!X){console.warn(`[phlex-reactive] reactive:defer target #${j} is not on the page — skipped`);return}let Z=Q.getAttribute("data-reactive-defer-src");if(!Z)return;if(!globalThis.customElements?.get?.("pgbus-stream-source")){let J=Q.getAttribute("data-reactive-defer-token");if(J){B(j,J);return}console.error("[phlex-reactive] reactive:defer via=stream but <pgbus-stream-source> is not registered "+"and no fallback token was provided — is the pgbus client loaded on this page?");return}f(j),p(X);let $=document.createElement("pgbus-stream-source");$.id=h(j),$.setAttribute("src",Z),$.setAttribute("since-id",Q.getAttribute("data-reactive-defer-since-id")??"0"),$.setAttribute("hidden",""),document.body.appendChild($),N.set(j,{via:"stream"})}function h(j){return`reactive-defer-src-${j}`}async function Zj(j,Q,X){let Z=setTimeout(()=>{Q.timedOut=!0,Q.abort.abort()},Gj()),$;try{$=await fetch($j(),{method:"POST",headers:{Accept:"text/vnd.turbo-stream.html","Content-Type":"application/json","X-CSRF-Token":Jj()},body:JSON.stringify({token:X}),credentials:"same-origin",signal:Q.abort.signal})}catch(G){if(clearTimeout(Z),N.get(j)!==Q)return;console.error("[phlex-reactive] deferred render failed",G),P(j,X);return}if(N.get(j)!==Q){clearTimeout(Z);return}if($.status===204){clearTimeout(Z),w(j);return}if(!$.ok){clearTimeout(Z),console.error(`[phlex-reactive] deferred render failed: HTTP ${$.status}`),P(j,X,$.status);return}let J;try{J=await $.text()}catch(G){if(clearTimeout(Z),N.get(j)!==Q)return;console.error("[phlex-reactive] deferred render failed reading the body",G),P(j,X);return}if(clearTimeout(Z),N.get(j)!==Q)return;w(j),window.Turbo.renderStreamMessage(J)}function f(j){let Q=N.get(j);if(!Q)return;if(N.delete(j),Q.via==="fetch")Q.abort.abort();else document.getElementById(h(j))?.remove?.()}function p(j){j.setAttribute("data-reactive-defer-pending","true"),j.setAttribute("aria-busy","true")}function u(j){j.removeAttribute("data-reactive-defer-pending"),j.removeAttribute("aria-busy")}function w(j){N.delete(j);let Q=document.getElementById(j);if(!Q)return;u(Q),Q.removeAttribute("data-reactive-error")}function P(j,Q,X){N.delete(j);let Z=document.getElementById(j);if(!Z)return;u(Z),Z.setAttribute("data-reactive-error","defer");let $=()=>{let J=document.getElementById(j);if(!J){console.warn("[phlex-reactive] defer retry() ignored — the target left the DOM");return}J.removeAttribute("data-reactive-error"),B(j,Q)};Z.dispatchEvent(new CustomEvent("reactive:error",{bubbles:!0,composed:!0,detail:{kind:"defer",target:j,status:X,retry:$}}))}function $j(){return document.querySelector('meta[name="phlex-reactive-defer-path"]')?.content||"/reactive/defer"}function Jj(){return document.querySelector('meta[name="csrf-token"]')?.content??""}function Gj(){let j=document.querySelector('meta[name="phlex-reactive-timeout"]')?.content,Q=Number(j);return Number.isFinite(Q)&&Q>0?Q:30000}var T=!1;function zj(){if(T)return;if(typeof document>"u"||!document.addEventListener)return;T=!0,document.addEventListener("turbo:before-stream-render",Kj)}function Kj(j){let Q=j.detail,X=Q?.render;if(typeof X!=="function"||X.__reactiveDismissWrapped){if(typeof requestAnimationFrame==="function")requestAnimationFrame(E);else setTimeout(E,0);return}let Z=async($)=>{await X($),E()};Z.__reactiveDismissWrapped=!0,Q.render=Z}function E(){let j=document.querySelectorAll("[data-reactive-dismiss-after]");for(let Q of j){if(Q.hasAttribute("data-reactive-dismiss-scheduled"))continue;let X=Number(Q.getAttribute("data-reactive-dismiss-after"));if(!Number.isFinite(X)||X<=0)continue;Q.setAttribute("data-reactive-dismiss-scheduled",""),setTimeout(()=>Q.remove(),X)}}function wj(){T=!1}var D=!1;function qj(){if(D)return;if(typeof window>"u"||typeof document>"u")return;if(typeof window.addEventListener!=="function")return;D=!0;let j=()=>{let Q=document.documentElement;if(typeof Q?.toggleAttribute!=="function")return;Q.toggleAttribute("data-reactive-offline",globalThis.navigator?.onLine===!1)};j(),window.addEventListener("online",j),window.addEventListener("offline",j)}function bj(){D=!1}var I="phlex-reactive:latency",x=!1;function Yj(j){if(typeof sessionStorage>"u")return;sessionStorage.setItem(I,String(j))}function Uj(){if(typeof sessionStorage>"u")return;sessionStorage.removeItem(I),x=!1}function Hj(){if(typeof window>"u"||typeof document>"u")return;if(document.querySelector?.('meta[name="phlex-reactive-env"]')?.content!=="development")return;window.PhlexReactive={enableLatencySim:Yj,disableLatencySim:Uj}}function vj(){x=!1}function b(){r(),t(),e(),jj(),zj(),qj(),Hj()}function Wj(j){return j.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}if(typeof window<"u")if(window.Turbo)b();else document.addEventListener("turbo:load",b,{once:!0});var O=!1;function Aj(){if(O)return;if(typeof document>"u")return;let j=document.querySelectorAll('[data-controller~="reactive"]');if(!j||j.length===0)return;console.warn("[phlex-reactive] found "+j.length+' element(s) with data-controller="reactive" '+"but the reactive controller never connected. It is loaded but not registered — "+'add `application.register("reactive", ReactiveController)` (importmap) or import it into app/javascript/controllers/ for lazyLoadControllersFrom apps. See the README.')}function yj(){O=!1}function hj(){O=!0}if(typeof window<"u"&&typeof document<"u"){let j=()=>setTimeout(Aj,0);if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",j,{once:!0});else j()}var _j=new Set(["href","src","srcdoc","action","formaction","xlink:href","style"]);function Lj(j){let Q=String(j).toLowerCase();return Q.startsWith("on")||_j.has(Q)}function Nj(j,Q,X){let[Z,$,J]=Q;j.classList.add(Z,$),X(),requestAnimationFrame(()=>{j.classList.remove($),j.classList.add(J)});let G=!1,K=()=>{if(G)return;G=!0,j.classList.remove(Z,J)};j.addEventListener("animationend",K,{once:!0}),setTimeout(K,350)}var v=Object.freeze({show:(j,Q)=>C(j,!1,Q),hide:(j,Q)=>C(j,!0,Q),toggle:(j,Q)=>C(j,!j.hidden,Q),add_class:(j,Q)=>j.classList.add(...Q.classes??[]),remove_class:(j,Q)=>j.classList.remove(...Q.classes??[]),toggle_class:(j,Q)=>(Q.classes??[]).forEach((X)=>j.classList.toggle(X)),set_attr:(j,Q)=>{if(F(Q.name))j.setAttribute(Q.name,Q.value??"")},remove_attr:(j,Q)=>{if(F(Q.name))j.removeAttribute(Q.name)},toggle_attr:(j,Q)=>{if(!F(Q.name))return;if(j.hasAttribute(Q.name))j.removeAttribute(Q.name);else j.setAttribute(Q.name,"")},focus:(j)=>j.focus?.(),focus_first:(j)=>Cj(j)?.focus?.(),text:(j,Q)=>{let X=String(Q.value??"");if(j.textContent!==X)j.textContent=X},dispatch:(j,Q)=>{j.dispatchEvent(new CustomEvent(Q.name,{bubbles:!0,composed:!0,detail:Q.detail??{}}))}});function C(j,Q,X){if(X?.transition)Nj(j,X.transition,()=>j.hidden=Q);else j.hidden=Q}function F(j){if(!Lj(j))return!0;return console.warn(`[phlex-reactive] refused client attr op on ${JSON.stringify(j)} — skipped`),!1}var m=/^#[A-Za-z_][\w-]*$/;function Vj(j){if(typeof j==="string"&&m.test(j))return!0;return console.warn(`[phlex-reactive] refused cross-root mirror target ${JSON.stringify(j)} — skipped`),!1}function Mj(j,Q){let X=j.getAttribute("data-reactive-show-equals");if(X!==null)return Q===X;let Z=j.getAttribute("data-reactive-show-not");if(Z!==null)return Q!==Z;let $=j.getAttribute("data-reactive-show-in");if($!==null){try{let J=JSON.parse($);if(Array.isArray(J))return J.includes(Q)}catch{}return console.warn(`[phlex-reactive] malformed reactive_show in: list ${JSON.stringify($)} — skipped`),null}for(let J of g){let G=j.getAttribute(`data-reactive-show-${J}`);if(G!==null)return c(J,G,Q)}return console.warn("[phlex-reactive] a reactive_show binding declares no predicate — skipped"),null}var g=["gte","gt","lte","lt"];function c(j,Q,X){let Z=Number(Q);if(Number.isNaN(Z))return console.warn(`[phlex-reactive] reactive_show ${j}: needs a numeric literal, got ${JSON.stringify(Q)} — skipped`),null;let $=X==null?"":String(X).trim(),J=$===""?NaN:Number($);if(Number.isNaN(J))return!1;switch(j){case"gte":return J>=Z;case"gt":return J>Z;case"lte":return J<=Z;case"lt":return J<Z;default:return null}}function d(j,Q){if(!j||typeof j!=="object")return null;if(typeof j.equals==="string")return Q===j.equals;if(typeof j.not==="string")return Q!==j.not;if(Array.isArray(j.in))return j.in.includes(Q);for(let X of g)if(X in j)return c(X,j[X],Q);return null}var y="[data-reactive-show-field], [data-reactive-show]";function xj(j){try{let Q=JSON.parse(j);if(Q&&typeof Q==="object"&&!Array.isArray(Q))return Q}catch{}return console.warn(`[phlex-reactive] malformed compound reactive_show payload ${JSON.stringify(j)} — skipped`),null}function S(j,Q){if(!j||typeof j!=="object"||typeof j.field!=="string")return!1;let X=Q(j.field)??"";return d(j,X)===!0}function l(j,Q){if(!Array.isArray(j)||j.length===0)return null;return j.some((X)=>Array.isArray(X)&&X.length>0&&X.every((Z)=>S(Z,Q)))}function Bj(j,Q){if(!j||typeof j!=="object")return null;let X=j.any;if(Array.isArray(X)&&(X.length===0||Array.isArray(X[0])))return l(X,Q);return Oj(j,Q)}function Oj(j,Q){let X=Array.isArray(j.all)?"all":Array.isArray(j.any)?"any":null;if(!X)return null;let Z=j[X];if(Z.length===0)return null;let $=Z.map((J)=>S(J,Q));return X==="all"?$.every(Boolean):$.some(Boolean)}function Pj(j){if(typeof j==="string"&&m.test(j))return!0;return console.warn(`[phlex-reactive] refused cross-root show target ${JSON.stringify(j)} — skipped`),!1}var Ej='a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';function Cj(j){return j.querySelectorAll?.(Ej)?.[0]??null}function o(j){if(Array.isArray(j))return j;if(typeof j!=="string")return[];try{let Q=JSON.parse(j);return Array.isArray(Q)?Q:[]}catch{return[]}}function s(j,Q){for(let X of j){if(!Array.isArray(X))continue;let[Z,$={}]=X;if(!Object.hasOwn(v,Z)){console.warn(`[phlex-reactive] unknown client op ${JSON.stringify(Z)} — skipped`);continue}for(let J of Q($))v[Z](J,$)}}function Fj(j,Q){let X=j.to;if(Q){if(X==="@root")return[Q];if(typeof X!=="string"||X==="")return[];if(j.global)return[...document.querySelectorAll(X)];return[...Q.querySelectorAll(X)]}if(typeof X!=="string"||X===""||X==="@root")return[];return[...document.querySelectorAll(X)]}class fj extends i{static values={token:String};#R;#_=new Map;#K=new Map;#e;#x;#T;#B=0;#q=new Map;#O=new WeakMap;#L=new Map;#D=new WeakSet;#Y;#U;#H;#j;#X;#W;#N;connect(){if(O=!0,this.element.id==="")console.warn("[phlex-reactive] a reactive root has no id; its next-action token can't self-match "+"and may fall back to the first token in the response → a silent HTTP 403 on the NEXT action. "+"Put id: on the SAME element as reactive_attrs — use div(**reactive_root) (emits id + token together), "+"or div(id:, **reactive_attrs). The id: must NOT be on a child. See the README.");if(this.element.getAttribute?.("data-reactive-defer-token"))this.#I(),this.#N=()=>this.#I(),this.element.addEventListener?.("turbo:morph-element",this.#N);if(this.#jj()){if(this.#Y=()=>this.#C(),this.element.addEventListener?.("turbo:morph-element",this.#Y),this.#C(),this.element.getAttribute?.("data-reactive-warn-unsaved")==="true")this.#xj()}if(this.#Oj())this.#j=()=>this.#g(),this.element.addEventListener?.("input",this.#j),this.element.addEventListener?.("change",this.#j),this.element.addEventListener?.("turbo:morph-element",this.#j),this.#g();if(this.#Fj())this.#X=(j)=>{if(j?.type==="input"&&!this.#Tj(j))return;this.#l()},this.element.addEventListener?.("input",this.#X),this.element.addEventListener?.("turbo:morph-element",this.#X),this.#l();if(this.#Rj())this.#W=()=>this.recompute(),this.element.addEventListener?.("turbo:morph-element",this.#W),this.recompute()}#jj(){if((this.element.getAttribute?.("data-action")??"").includes("reactive#trackDirty"))return!0;let j=this.element.querySelectorAll?.('[data-action*="reactive#trackDirty"]')??[];for(let Q of j)if(this.#Q(Q))return!0;return!1}disconnect(){if(this.#qj(),this.#Uj(),this.#Bj(),this.#Cj(),this.#Dj(),this.#Ij(),this.#N)this.element.removeEventListener?.("turbo:morph-element",this.#N)}#I(){let j=this.element;if(!j?.id)return;let Q=j.getAttribute?.("data-reactive-defer-token");if(!Q)return;if(j.getAttribute?.("data-reactive-defer-pending")!=="true")return;B(j.id,Q)}dispatch(j){let{action:Q,params:X,debounce:Z,throttle:$,confirm:J,confirmWhen:G,outside:K,window:z,optimistic:Y}=j.params;if(!Q)return;let H=j.params.busy??this.#uj(j.params.loading);if(K&&this.element.contains(j.target))return;let _=j.currentTarget??j.target;if(!z&&!this.#wj(Y,_))j.preventDefault();let W=this.#p(J,G);if(!W)return this.#b(_,Q,X,Z,$,Y,H);Promise.resolve().then(()=>k(W)).catch(()=>!1).then((A)=>{if(A)this.#b(_,Q,X,Z,$,Y,H)})}runOps(j){let{ops:Q,confirm:X,confirmWhen:Z,outside:$,window:J}=j.params;if($&&this.element.contains(j.target))return;if(!J)j.preventDefault();let G=this.#p(X,Z);if(!G)return this.#i(this.#s(Q));Promise.resolve().then(()=>k(G)).catch(()=>!1).then((K)=>{if(K)this.#i(this.#s(Q))})}trackDirty(){this.#C()}recompute(j){if(j&&this.#D.has(j))return;let Q=this.#Xj(),X=Q.map(([q])=>q),Z=this.element.getAttribute?.("data-reactive-scope")||null,$=(q)=>Z&&!q.includes("[")?`${Z}[${q}]`:q,J=this.#A(),G=new Map,K=(q)=>{if(G.has(q))return G.get(q);let U=null;for(let V of this.element.querySelectorAll(`[name="${$(q)}"]`))if(J(V)){U=V;break}return G.set(q,U),U};for(let q of X)this.#k(q,K(q)?.value??"");let z=this.element.getAttribute("data-reactive-compute-reducer-param"),Y=z?n(z):null;if(!Y){this.#w({},K);return}let H=this.#Qj("data-reactive-compute-outputs-param"),_={};for(let[q,U]of Q){let V=K(q);if(U==="string")_[q]=V?.value??"";else{let L=Number(V?.value);_[q]=Number.isFinite(L)?L:0}}let W=Y(_,{changed:this.#Zj(j,X,Z)})||{},A=[];for(let q of H){if(!(q in W))continue;let U=K(q);if(!U)continue;if(String(W[q])===U.value)continue;U.value=W[q],A.push(U)}for(let q of Object.keys(W)){let U=W[q];if(U===void 0||U===null)continue;this.#k(q,U)}this.#w(W,K);for(let q of A){let U=new Event("input",{bubbles:!0});this.#D.add(U),q.dispatchEvent(U)}}listnavNext(j){this.#S(j,1)}listnavPrev(j){this.#S(j,-1)}listnavPick(j){let Q=this.#P(j),X=Q.findIndex((Z)=>Z.hasAttribute("data-reactive-highlighted"));if(X<0)return;j.preventDefault(),Q[X].click()}listnavClose(j){for(let Q of this.#P(j))Q.removeAttribute("data-reactive-highlighted")}#S(j,Q){let X=this.#P(j);if(!X.length)return;j.preventDefault();let Z=X.findIndex((G)=>G.hasAttribute("data-reactive-highlighted")),$=Z<0?Q>0?0:X.length-1:(Z+Q+X.length)%X.length;for(let G of X)G.removeAttribute("data-reactive-highlighted");let J=X[$];J.setAttribute("data-reactive-highlighted","true"),J.scrollIntoView?.({block:"nearest"})}#P(j){let X=(j?.currentTarget??j?.target??this.element).getAttribute?.("data-reactive-listnav-option-param")??this.element.getAttribute("data-reactive-listnav-option-param");if(!X)return[];let Z=this.#A();return Array.from(this.element.querySelectorAll(X)).filter(($)=>!$.hidden&&Z($))}#Qj(j){let Q=this.element.getAttribute(j);if(!Q)return[];try{let X=JSON.parse(Q);return Array.isArray(X)?X:[]}catch{return[]}}#Xj(){let j=this.element.getAttribute("data-reactive-compute-inputs-param");if(!j)return[];try{let Q=JSON.parse(j);if(Array.isArray(Q))return Q.map((X)=>[X,"number"]);if(Q&&typeof Q==="object")return Object.entries(Q);return[]}catch{return[]}}#Zj(j,Q,X){let Z=j?.target;if(!Z?.name||typeof Z.closest!=="function")return null;let $=this.#$j(Z.name,X);if(!Q.includes($))return null;return this.#Q(Z)?$:null}#$j(j,Q){if(!Q)return j;let X=`${Q}[`;return j.startsWith(X)&&j.endsWith("]")?j.slice(X.length,-1):j}#k(j,Q){let X=String(Q);for(let Z of this.#Jj(j)){if(Z.textContent===X)continue;Z.textContent=X}}#Jj(j){let Q=this.element.querySelectorAll(`[data-reactive-text="${j}"]`);return Array.from(Q).filter((X)=>this.#Q(X))}#w(j,Q){let X=this.#Gj();for(let[Z,$]of Object.entries(X)){let J=Z in j?j[Z]:Q(Z)?.value;if(J===void 0||J===null)continue;let G=String(J);for(let K of Array.isArray($)?$:[$]){if(!Vj(K))continue;for(let z of document.querySelectorAll(K)){if(z.textContent===G)continue;z.textContent=G}}}}#Gj(){let j=this.element.getAttribute("data-reactive-compute-mirror-param");if(!j)return{};try{let Q=JSON.parse(j);return Q&&typeof Q==="object"&&!Array.isArray(Q)?Q:{}}catch{return{}}}#b(j,Q,X,Z,$,J,G){if(this.#M("reactive:before-dispatch",{action:Q,params:this.#o(X),element:this.element},{cancelable:!0}).defaultPrevented)return;let z=Number(Z)||0;if(z>0)return this.#Kj(j,z,Q,X,J,G);let Y=Number($)||0;if(Y>0)return this.#Yj(j,Y,Q,X,J,G);return this.#V(Q,X,J,j,G)}#V(j,Q,X,Z,$){let J=this.#bj(X,Z),G=this.#vj(j,Z,$),K=this.#v()?this.#zj(X,Z):null;return this.queue=(this.queue??Promise.resolve()).then(()=>this.#Nj(j,Q,J,G,K)),this.queue}#zj(j,Q){if(!j?.hide)return null;let X=this.#r(j,Q);if(!X.length)return null;return()=>{let Z=X.filter(($)=>$.isConnected&&!$.hidden);if(!Z.length)return;console.warn("[phlex-reactive] optimistic: { hide: true } was undone by the reply's re-render — "+"the element is visible again. For an instant delete, return reply.remove so the server removes it; otherwise the hide only flashes.",Z)}}#Kj(j,Q,X,Z,$,J){this.#E(j);let G=()=>{this.#E(j),this.#V(X,Z,$,j,J)},K=setTimeout(G,Q);j?.addEventListener?.("blur",G,{once:!0}),this.#_.set(j,{timer:K,flush:G})}#E(j){let Q=this.#_.get(j);if(!Q)return;clearTimeout(Q.timer),j?.removeEventListener?.("blur",Q.flush),this.#_.delete(j)}#qj(){for(let j of[...this.#_.keys()])this.#E(j)}#Yj(j,Q,X,Z,$,J){let G=this.#K.get(j)??new Map;if(G.has(X))return;let K=setTimeout(()=>{if(G.delete(X),G.size===0)this.#K.delete(j)},Q);return G.set(X,K),this.#K.set(j,G),this.#V(X,Z,$,j,J)}#Uj(){for(let j of this.#K.values())for(let Q of j.values())clearTimeout(Q);this.#K.clear()}#M(j,Q,{cancelable:X=!1}={}){let Z=new CustomEvent(j,{bubbles:!0,composed:!0,cancelable:X,detail:Q});return(this.element.isConnected?this.element:document).dispatchEvent(Z),Z}#Z(j,Q,X,Z){let $=()=>{if(!this.element.isConnected){console.warn("[phlex-reactive] retry() ignored — the reactive root left the DOM");return}return this.#V(j,Q)};this.#M("reactive:error",{action:j,params:X,...Z,retry:$})}#$(j){if(this.element?.isConnected===!1)return;this.element?.setAttribute?.("data-reactive-error",j)}#Hj(){this.element?.removeAttribute?.("data-reactive-error")}#Wj(){let j=document.querySelector("[data-reactive-error-flash]");if(!j?.content)return;let Q=j.getAttribute("data-reactive-error-flash")||"flash",X=document.getElementById(Q);if(!X)return;X.appendChild(j.content.cloneNode(!0))}#Aj(){if(typeof sessionStorage>"u")return Promise.resolve();let j=Number(sessionStorage.getItem(I));if(!Number.isFinite(j)||j<=0)return Promise.resolve();if(!x)x=!0,console.warn(`[phlex-reactive] latency simulator ACTIVE — every action is delayed by ${j}ms. Call PhlexReactive.disableLatencySim() (or clear sessionStorage) to turn it off.`);return new Promise((Q)=>setTimeout(Q,j))}#v(){return this.element?.getAttribute?.("data-reactive-debug")==="true"}#y(){return typeof performance<"u"&&typeof performance.now==="function"?performance.now():Date.now()}#_j(j){if(!j)return[];let Q=[],X=/<turbo-stream\b([^>]*)>/g,Z;while((Z=X.exec(j))!==null){let $=Z[1],J=$.match(/\baction="([^"]*)"/)?.[1]??"?",G=$.match(/\btarget="([^"]*)"/)?.[1];Q.push(G?`${J} → #${G}`:J)}return Q}#Lj(j){let{action:Q,status:X,ms:Z}=j,J=`reactive ${this.element?.id?`#${this.element.id} `:""}${Q} → ${X??"—"} (${Math.round(Z)}ms)`;if(console.groupCollapsed(J),console.log(`params: [${j.paramNames.join(", ")}] + collected: [${j.fieldNames.join(", ")}]`),console.log(`encoding: ${j.encoding}`),j.streams.length)console.log(`streams: ${j.streams.join(" ")}`);console.log(`token: ${j.tokenRefreshed?"refreshed ✓":"unchanged"}`),console.groupEnd()}async#Nj(j,Q,X,Z,$){let{fields:J,files:G}=this.#u(),K=this.#o(Q),z={...J,...K},Y=this.#J,H=G.length>0,_=H?this.#Sj(Y,j,z,G):JSON.stringify({token:Y,act:j,params:z}),W=this.#v()?{action:j,paramNames:Object.keys(K),fieldNames:Object.keys(J),encoding:H?"multipart":"json",status:null,streams:[],tokenRefreshed:!1,started:this.#y()}:null;await this.#Aj();try{if(navigator.onLine===!1){this.#G(X),this.#$("offline"),this.#Z(j,Q,z,{kind:"offline"});return}let A;try{let L={Accept:"text/vnd.turbo-stream.html","X-CSRF-Token":this.#cj()};if(!H)L["Content-Type"]="application/json";let M=this.#dj();if(M)L["X-Pgbus-Connection"]=M;A=await fetch(this.#mj(),{method:"POST",headers:L,body:_,credentials:"same-origin",signal:AbortSignal.timeout(this.#gj())})}catch(L){if(console.error("[phlex-reactive] action error",L),this.#G(X),L?.name==="TimeoutError"||L?.name==="AbortError"){this.#$("timeout"),this.#Z(j,Q,z,{kind:"timeout"});return}this.#Wj(),this.#$("network"),this.#Z(j,Q,z,{kind:"network"});return}if(W)W.status=A.status;if(A.redirected){console.error("[phlex-reactive] action was redirected (auth/CSRF?) — no update applied"),this.#G(X),this.#$("redirected"),this.#Z(j,Q,z,{kind:"redirected",status:A.status});return}if(!A.ok){let L=await A.text();if(console.error(`[phlex-reactive] action failed: HTTP ${A.status}`,L),this.#G(X),(A.headers.get("Content-Type")||"").includes("turbo-stream")){let M=this.#f(L);if(this.#J=M??this.#J,W)this.#h(W,L,M);window.Turbo.renderStreamMessage(L)}this.#$("http"),this.#Z(j,Q,z,{kind:"http",status:A.status,body:L});return}let q=A.headers.get("Content-Type")||"";if(!q.includes("turbo-stream")){console.error(`[phlex-reactive] expected a turbo-stream, got "${q}" — no update applied`),this.#G(X),this.#$("content-type"),this.#Z(j,Q,z,{kind:"content-type",status:A.status});return}let U=await A.text(),V=this.#f(U);if(this.#J=V??this.#J,W)this.#h(W,U,V);if(window.Turbo.renderStreamMessage(U),$)queueMicrotask($);this.#Hj(),this.#M("reactive:applied",{action:j,params:z,html:U})}catch(A){console.error("[phlex-reactive] action error",A),this.#G(X),this.#M("reactive:error",{action:j,params:z,kind:"apply"})}finally{if(Z?.(),W)this.#Lj({...W,ms:this.#y()-W.started})}}#h(j,Q,X){j.streams=this.#_j(Q),j.tokenRefreshed=X!=null}get#J(){return this.#R??this.tokenValue}set#J(j){this.#R=j}#f(j){let Q=this.element.id;if(!Q)return j.match(/data-reactive-token-value="([^"]+)"/)?.[1];let{token:X,self:Z}=this.#Vj(Q),$=j.match(X);if($)return $[1];let J=j.match(Z);if(J)return J[1].match(/data-reactive-token-value="([^"]+)"/)?.[1];return}#Vj(j){let Q=this.#T;if(Q&&Q.id===j)return Q;let X=Wj(j);return this.#T={id:j,token:new RegExp(`<turbo-stream\\b[^>]*\\baction="reactive:token"[^>]*\\btarget="${X}"[^>]*\\bdata-reactive-token-value="([^"]+)"`),self:new RegExp(`<turbo-stream\\b[^>]*\\baction="(?:replace|update)"[^>]*\\btarget="${X}"[^>]*>([\\s\\S]*?)</turbo-stream>`)}}#Q(j){return j.closest('[data-controller~="reactive"]')===this.element}#A(){if(this.element.querySelectorAll('[data-controller~="reactive"]').length===0)return()=>!0;return(Q)=>this.#Q(Q)}#p(j,Q){if(j)return j;if(!Q)return null;let X=Q;if(typeof Q==="string")try{X=JSON.parse(Q)}catch{return console.warn(`[phlex-reactive] malformed conditional confirm payload ${JSON.stringify(Q)} — skipped`),null}if(!X||typeof X!=="object")return null;let{fields:Z}=this.#u(),$=(G)=>Z[G],J;if(typeof X.predicate==="string"){let G=a(X.predicate);if(!G)return console.warn(`[phlex-reactive] confirm predicate "${X.predicate}" is not registered — proceeding without a dialog (register it with setConfirmPredicate)`),null;J=!!G(Z)}else J=l(X.groups?.any,$)===!0;return J?X.message:null}#u(){let j={},Q=[],X=this.#A();return this.element.querySelectorAll("input[name], select[name], textarea[name]").forEach((Z)=>{if(!X(Z))return;if(Z.type==="file")for(let $ of Z.files??[])Q.push({name:Z.name,file:$,multiple:Z.multiple});else if(Z.type==="checkbox")j[Z.name]=Z.checked;else if(Z.type==="radio"){if(Z.checked)j[Z.name]=Z.value}else j[Z.name]=Z.value}),this.element.querySelectorAll("[name]:is(lexxy-editor, trix-editor, [contenteditable=''], [contenteditable=true], [contenteditable=plaintext-only])").forEach((Z)=>{if(!X(Z))return;let $=Z.getAttribute("name");if(!$)return;let J=j[$];if(J==null||J==="")j[$]=Z.value??Z.textContent??Z.innerHTML??""}),{fields:j,files:Q}}#C(){if(typeof this.element?.querySelectorAll!=="function")return;let j=0;if(this.element.querySelectorAll("input[name], select[name], textarea[name]").forEach((Q)=>{if(!this.#Q(Q))return;if(Q.type==="file")return;if(this.#Mj(Q))Q.setAttribute("data-reactive-dirty","true"),j++;else Q.removeAttribute("data-reactive-dirty")}),j>0)this.element.setAttribute("data-reactive-dirty",String(j));else this.element.removeAttribute("data-reactive-dirty")}#Mj(j){if(j.type==="checkbox"||j.type==="radio")return j.checked!==j.defaultChecked;if(j.tag==="select"||j.options)return Array.from(j.options??[]).some((Q)=>Q.selected!==Q.defaultSelected);return j.value!==j.defaultValue}#m(){let j=this.element.getAttribute?.("data-reactive-dirty"),Q=Number(j);return Number.isFinite(Q)&&Q>0?Q:0}#xj(){if(typeof window>"u"||typeof window.addEventListener!=="function")return;this.#U=(j)=>{if(this.#m()===0)return;return j.preventDefault(),j.returnValue="You have unsaved changes.",j.returnValue},this.#H=(j)=>{if(this.#m()===0)return;if(!(typeof window.confirm==="function"?window.confirm("You have unsaved changes. Leave anyway?"):!0))j.preventDefault?.()},window.addEventListener("beforeunload",this.#U),window.addEventListener("turbo:before-visit",this.#H)}#Bj(){if(this.#Y)this.element.removeEventListener?.("turbo:morph-element",this.#Y),this.#Y=void 0;if(typeof window<"u"&&typeof window.removeEventListener==="function"){if(this.#U)window.removeEventListener("beforeunload",this.#U);if(this.#H)window.removeEventListener("turbo:before-visit",this.#H)}this.#U=void 0,this.#H=void 0}#Oj(){if(this.element.getAttribute?.("data-reactive-show-targets"))return!0;let j=this.element.querySelectorAll?.(y)??[];for(let Q of j)if(this.#Q(Q))return!0;return!1}#g(){if(typeof this.element?.querySelectorAll!=="function")return;let j=this.#A(),Q=this.element.getAttribute?.("data-reactive-scope")||null,X=new Map,Z=($)=>{if(!X.has($))X.set($,this.#d($,j,Q));return X.get($)};for(let $ of this.element.querySelectorAll(y)){if(!j($))continue;let J=$.getAttribute("data-reactive-show");if(J!==null){let Y=Bj(xj(J),Z);if(Y!==null)this.#c($,Y,j,Q);continue}let G=$.getAttribute("data-reactive-show-field");if(!G)continue;let K=Z(G);if(K===null)continue;let z=Mj($,K);if(z===null)continue;this.#c($,z,j,Q)}this.#Pj(j,X,Q)}#c(j,Q,X,Z){if(j.hidden=!Q,j.getAttribute("data-reactive-show-disable")!=="true")return;if(typeof j.querySelectorAll!=="function")return;for(let $ of j.querySelectorAll("input[name], select[name], textarea[name]"))if(X($))$.disabled=!Q;if(j.name&&X(j))j.disabled=!Q}#Pj(j,Q,X){let Z=this.#Ej();for(let[$,J]of Object.entries(Z)){if(!J||typeof J!=="object"||Array.isArray(J))continue;if(!Q.has($))Q.set($,this.#d($,j,X));let G=Q.get($);if(G===null)continue;let K=()=>G;for(let[z,Y]of Object.entries(J)){if(!Pj(z))continue;let H;if(Array.isArray(Y)){if(Y.length===0){console.warn(`[phlex-reactive] malformed reactive_show_targets group for ${z} — skipped`);continue}H=Y.every((_)=>S(_,K))}else{let _=d(Y,G);if(_===null){console.warn(`[phlex-reactive] malformed reactive_show_targets predicate for ${z} — skipped`);continue}H=_}for(let _ of document.querySelectorAll(z))_.hidden=!H}}}#Ej(){let j=this.element.getAttribute?.("data-reactive-show-targets");if(!j)return{};try{let Q=JSON.parse(j);if(Q&&typeof Q==="object"&&!Array.isArray(Q))return Q}catch{}return console.warn("[phlex-reactive] malformed data-reactive-show-targets — ignored. "+"Did two reactive_show_targets calls collide on one root? Declare every field in ONE call: reactive_show_targets(mode: { ... }, kind: { ... })"),{}}#d(j,Q,X){let Z=X&&!j.includes("[")?`${X}[${j}]`:j,$=!1,J=null;for(let G of this.element.querySelectorAll(`[name="${Z}"]`)){if(!Q(G))continue;if(G.type==="checkbox")return G.checked?"true":"false";if(G.type==="radio"){if(G.checked)return G.value??"";$=!0;continue}J??=G}if(J)return J.value??"";return $?"":null}#Cj(){if(!this.#j)return;this.element.removeEventListener?.("input",this.#j),this.element.removeEventListener?.("change",this.#j),this.element.removeEventListener?.("turbo:morph-element",this.#j),this.#j=void 0}#Fj(){return!!(this.element.getAttribute?.("data-reactive-filter-input")&&this.element.getAttribute?.("data-reactive-filter-option"))}#Rj(){return this.element.getAttribute?.("data-reactive-compute-seed")==="true"}#Tj(j){let Q=this.element.getAttribute("data-reactive-filter-input");return!!Q&&typeof j.target?.matches==="function"&&j.target.matches(Q)}#l(){if(typeof this.element?.querySelectorAll!=="function")return;let j=this.element.getAttribute("data-reactive-filter-input"),Q=this.element.getAttribute("data-reactive-filter-option");if(!j||!Q)return;let X=this.#A(),Z=[...this.element.querySelectorAll(j)].find(X);if(!Z)return;let $=(Z.value??"").trim().toLowerCase(),J=0;for(let z of this.element.querySelectorAll(Q)){if(!X(z))continue;let Y=(z.getAttribute("data-reactive-filter-text")??z.textContent??"").toLowerCase(),H=$!==""&&!Y.includes($);if(z.hidden=H,H)z.removeAttribute("data-reactive-highlighted");else J++}let G=this.element.getAttribute("data-reactive-filter-group");if(G)for(let z of this.element.querySelectorAll(G)){if(!X(z))continue;let Y=[...z.querySelectorAll(Q)].filter(X);if(Y.length===0)continue;z.hidden=Y.every((H)=>H.hidden)}let K=this.element.getAttribute("data-reactive-filter-empty");if(K){for(let z of this.element.querySelectorAll(K))if(X(z))z.hidden=J>0}}#Dj(){if(!this.#X)return;this.element.removeEventListener?.("input",this.#X),this.element.removeEventListener?.("turbo:morph-element",this.#X),this.#X=void 0}#Ij(){if(!this.#W)return;this.element.removeEventListener?.("turbo:morph-element",this.#W),this.#W=void 0}#Sj(j,Q,X,Z){let $=new FormData;$.append("token",j),$.append("act",Q);for(let[G,K]of Object.entries(X))this.#F($,`params[${G}]`,K);let J=this.#kj(Z);for(let{name:G,file:K,multiple:z}of Z){let H=z||J.has(G)?`params[${G}][]`:`params[${G}]`;$.append(H,K,K.name)}return $}#F(j,Q,X){if(X==null)j.append(Q,"");else if(Array.isArray(X))X.forEach((Z,$)=>this.#F(j,`${Q}[${$}]`,Z));else if(typeof X==="object")for(let[Z,$]of Object.entries(X))this.#F(j,`${Q}[${Z}]`,$);else j.append(Q,String(X))}#kj(j){let Q=new Map;for(let{name:X}of j)Q.set(X,(Q.get(X)??0)+1);return new Set([...Q].filter(([,X])=>X>1).map(([X])=>X))}#o(j){if(!j)return{};try{return typeof j==="string"?JSON.parse(j):j}catch{return{}}}#s(j){return o(j)}#i(j){s(j,(Q)=>this.#n(Q))}#n(j){let Q=j.to;if(Q==="@root")return[this.element];if(typeof Q!=="string"||Q==="")return[];if(j.global)return[...document.querySelectorAll(Q)];return[...this.element.querySelectorAll(Q)].filter((X)=>this.#Q(X))}#wj(j,Q){if(j?.checked!=="keep")return!1;let X=Q?.type;return X==="checkbox"||X==="radio"}#bj(j,Q){if(!j)return null;let X=this.#a(j,Q,!0);return X.length?X:null}#G(j){if(!j)return;if(!this.element.isConnected)return;for(let Q of j)Q()}#vj(j,Q,X){this.#hj(j,Q);let Z=X?this.#a(X,Q,!1):[],$=!1;return()=>{if($)return;$=!0,this.#fj(j,Q);for(let J of Z)J()}}#a(j,Q,X){let Z=[];for(let $ of this.#r(j,Q)){if(j.add_class){let J=j.add_class.filter((G)=>!$.classList.contains(G));if($.classList.add(...J),J.length)Z.push(()=>$.classList.remove(...J))}if(j.remove_class){let J=j.remove_class.filter((G)=>$.classList.contains(G));if($.classList.remove(...J),J.length)Z.push(()=>$.classList.add(...J))}if(j.toggle_class)j.toggle_class.forEach((J)=>$.classList.toggle(J)),Z.push(()=>j.toggle_class.forEach((J)=>$.classList.toggle(J)));if(j.hide)$.hidden=!0,Z.push(()=>$.hidden=!1);if(j.show)$.hidden=!1,Z.push(()=>$.hidden=!0)}if(Q&&(j.disable||j.text!=null))Z.push(this.#yj(j,Q));if(X&&j.checked==="keep"&&Q&&"checked"in Q){let $=Q.checked;Z.push(()=>Q.checked=!$)}return Z}#r(j,Q){if(j.to==null)return Q?[Q]:[];return this.#n({to:j.to})}#yj(j,Q){let X=this.#L.get(Q);if(X)X.count++;else this.#L.set(Q,{count:1,disabled:Q.disabled,html:Q.innerHTML,hadText:j.text!=null,swappedTo:j.text});if(j.disable)Q.disabled=!0;if(j.text!=null)Q.innerHTML=j.text;return()=>this.#pj(Q,j)}#hj(j,Q){if(this.#z(Q,j,1),this.#z(this.element,j,1),this.#q.set(j,(this.#q.get(j)??0)+1),this.#B++===0)this.element.setAttribute("aria-busy","true");for(let X of this.#t(j))this.#z(X,j,1)}#fj(j,Q){this.#z(Q,j,-1),this.#z(this.element,j,-1);let X=(this.#q.get(j)??1)-1;if(X<=0)this.#q.delete(j);else this.#q.set(j,X);if(--this.#B<=0)this.#B=0,this.element.removeAttribute("aria-busy");for(let Z of this.#t(j))this.#z(Z,j,-1)}#z(j,Q,X){if(!j||typeof j.getAttribute!=="function")return;let Z=this.#O.get(j)??new Map,$=(Z.get(Q)??0)+X;if($<=0)Z.delete(Q);else Z.set(Q,$);if(Z.size===0){this.#O.delete(j),j.removeAttribute("data-reactive-busy");return}this.#O.set(j,Z),j.setAttribute("data-reactive-busy",[...Z.keys()].join(" "))}#t(j){return[...this.element.querySelectorAll?.("[data-reactive-busy-on]")??[]].filter((X)=>X.getAttribute("data-reactive-busy-on")===j&&this.#Q(X))}#pj(j,Q){let X=this.#L.get(j);if(!X)return;if(--X.count>0)return;if(this.#L.delete(j),!j.isConnected)return;if(Q.disable)j.disabled=X.disabled;if(X.hadText&&j.innerHTML===X.swappedTo)j.innerHTML=X.html}#uj(j){if(!j||typeof j!=="object")return null;let{class:Q,...X}=j;return Q==null?j:{...X,add_class:Q}}#mj(){return this.#e??=document.querySelector('meta[name="phlex-reactive-action-path"]')?.content||"/reactive/actions"}#gj(){if(this.#x!=null)return this.#x;let j=document.querySelector('meta[name="phlex-reactive-timeout"]')?.content,Q=Number(j);return this.#x=Number.isFinite(Q)&&Q>0?Q:30000}#cj(){return document.querySelector('meta[name="csrf-token"]')?.content??""}#dj(){return document.querySelector("pgbus-stream-source[connection-id]")?.getAttribute("connection-id")||document.querySelector('meta[name="pgbus-connection-id"]')?.content||null}}export{Sj as resetReactiveDefers,r as registerReactiveVisit,t as registerReactiveToken,qj as registerReactiveOffline,e as registerReactiveJs,zj as registerReactiveDismiss,jj as registerReactiveDefer,b as registerReactiveActions,kj as pendingDeferVia,Wj as escapeRegExp,Yj as enableLatencySim,Uj as disableLatencySim,fj as default,Aj as checkReactiveRegistration,yj as __resetReactiveRegistrationForTest,bj as __resetReactiveOfflineForTest,vj as __resetReactiveLatencyForTest,wj as __resetReactiveDismissForTest,hj as __markReactiveConnectedForTest,I as LATENCY_KEY};
|
|
1
|
+
import{Controller as Qj}from"@hotwired/stimulus";import{confirmResolver as b}from"phlex/reactive/confirm";import{computeReducer as Xj}from"phlex/reactive/compute";import{confirmPredicate as Zj}from"phlex/reactive/confirm_predicate";function $j(){let j=window.Turbo?.StreamActions;if(!j||j["reactive:visit"])return;j["reactive:visit"]=function(){let Q=this.getAttribute("data-url");if(Q)window.Turbo.visit(Q,{action:"advance"})}}function Jj(){let j=window.Turbo?.StreamActions;if(!j||j["reactive:token"])return;j["reactive:token"]=function(){let Q=this.getAttribute("data-reactive-token-value"),X=this.getAttribute("target");if(!Q||!X)return;let Z=document.getElementById(X);if(Z)Z.setAttribute("data-reactive-token-value",Q)}}function Gj(){let j=window.Turbo?.StreamActions;if(!j||j["reactive:js"])return;j["reactive:js"]=function(){let Q=e(this.getAttribute("data-reactive-ops"));if(!Q.length)return;let X=this.getAttribute("target"),Z=X?document.getElementById(X):null;if(X&&!Z)return;jj(Q,($)=>wj($,Z))}}var V=new Map;function p(j,Q){let X=!V.has(j);if(V.set(j,Q),X)l()}function O(j){if(V.delete(j))o()}function fj(){V.clear(),D=!1}function pj(j){return V.get(j)?.via}var D=!1;function zj(){let j=window.Turbo?.StreamActions;if(!j||j["reactive:defer"])return;if(j["reactive:defer"]=function(){let Q=this.getAttribute("target");if(!Q)return;if(this.getAttribute("data-reactive-defer-via")==="stream"){qj(Q,this);return}let X=this.getAttribute("data-reactive-defer-token");if(!X)return;P(Q,X)},!D&&typeof document<"u"&&document.addEventListener)D=!0,document.addEventListener("turbo:before-stream-render",Kj)}function Kj(j){let X=j.target?.getAttribute?.("target");if(!X)return;let Z=X.startsWith("reactive-defer-src-")?X.slice(19):X;if(V.get(Z)?.via==="stream")O(Z)}function P(j,Q){let X=document.getElementById(j);if(!X){console.warn(`[phlex-reactive] reactive:defer target #${j} is not on the page — skipped`);return}m(j),g(X);let Z={via:"fetch",abort:new AbortController,timedOut:!1};p(j,Z),Yj(j,Z,Q)}function qj(j,Q){let X=document.getElementById(j);if(!X){console.warn(`[phlex-reactive] reactive:defer target #${j} is not on the page — skipped`);return}let Z=Q.getAttribute("data-reactive-defer-src");if(!Z)return;if(!globalThis.customElements?.get?.("pgbus-stream-source")){let J=Q.getAttribute("data-reactive-defer-token");if(J){P(j,J);return}console.error("[phlex-reactive] reactive:defer via=stream but <pgbus-stream-source> is not registered "+"and no fallback token was provided — is the pgbus client loaded on this page?");return}m(j),g(X);let $=document.createElement("pgbus-stream-source");$.id=u(j),$.setAttribute("src",Z),$.setAttribute("since-id",Q.getAttribute("data-reactive-defer-since-id")??"0"),$.setAttribute("hidden",""),document.body.appendChild($),p(j,{via:"stream"})}function u(j){return`reactive-defer-src-${j}`}async function Yj(j,Q,X){let Z=setTimeout(()=>{Q.timedOut=!0,Q.abort.abort()},Wj()),$;try{$=await fetch(Uj(),{method:"POST",headers:{Accept:"text/vnd.turbo-stream.html","Content-Type":"application/json","X-CSRF-Token":Hj()},body:JSON.stringify({token:X}),credentials:"same-origin",signal:Q.abort.signal})}catch(G){if(clearTimeout(Z),V.get(j)!==Q)return;console.error("[phlex-reactive] deferred render failed",G),F(j,X);return}if(V.get(j)!==Q){clearTimeout(Z);return}if($.status===204){clearTimeout(Z),v(j);return}if(!$.ok){clearTimeout(Z),console.error(`[phlex-reactive] deferred render failed: HTTP ${$.status}`),F(j,X,$.status);return}let J;try{J=await $.text()}catch(G){if(clearTimeout(Z),V.get(j)!==Q)return;console.error("[phlex-reactive] deferred render failed reading the body",G),F(j,X);return}if(clearTimeout(Z),V.get(j)!==Q)return;v(j),window.Turbo.renderStreamMessage(J)}function m(j){let Q=V.get(j);if(!Q)return;if(O(j),Q.via==="fetch")Q.abort.abort();else document.getElementById(u(j))?.remove?.()}function g(j){j.setAttribute("data-reactive-defer-pending","true"),j.setAttribute("aria-busy","true")}function c(j){j.removeAttribute("data-reactive-defer-pending"),j.removeAttribute("aria-busy")}function v(j){O(j);let Q=document.getElementById(j);if(!Q)return;c(Q),Q.removeAttribute("data-reactive-error")}function F(j,Q,X){O(j);let Z=document.getElementById(j);if(!Z)return;c(Z),Z.setAttribute("data-reactive-error","defer");let $=()=>{let J=document.getElementById(j);if(!J){console.warn("[phlex-reactive] defer retry() ignored — the target left the DOM");return}J.removeAttribute("data-reactive-error"),P(j,Q)};Z.dispatchEvent(new CustomEvent("reactive:error",{bubbles:!0,composed:!0,detail:{kind:"defer",target:j,status:X,retry:$}}))}function Uj(){return document.querySelector('meta[name="phlex-reactive-defer-path"]')?.content||"/reactive/defer"}function Hj(){return document.querySelector('meta[name="csrf-token"]')?.content??""}function Wj(){let j=document.querySelector('meta[name="phlex-reactive-timeout"]')?.content,Q=Number(j);return Number.isFinite(Q)&&Q>0?Q:30000}var I=!1;function _j(){if(I)return;if(typeof document>"u"||!document.addEventListener)return;I=!0,document.addEventListener("turbo:before-stream-render",Lj)}function Lj(j){let Q=j.detail,X=Q?.render;if(typeof X!=="function"||X.__reactiveDismissWrapped){if(typeof requestAnimationFrame==="function")requestAnimationFrame(C);else setTimeout(C,0);return}let Z=async($)=>{await X($),C()};Z.__reactiveDismissWrapped=!0,Q.render=Z}function C(){let j=document.querySelectorAll("[data-reactive-dismiss-after]");for(let Q of j){if(Q.hasAttribute("data-reactive-dismiss-scheduled"))continue;let X=Number(Q.getAttribute("data-reactive-dismiss-after"));if(!Number.isFinite(X)||X<=0)continue;Q.setAttribute("data-reactive-dismiss-scheduled",""),setTimeout(()=>Q.remove(),X)}}function uj(){I=!1}var S=!1;function Nj(){if(S)return;if(typeof window>"u"||typeof document>"u")return;if(typeof window.addEventListener!=="function")return;S=!0;let j=()=>{let Q=document.documentElement;if(typeof Q?.toggleAttribute!=="function")return;Q.toggleAttribute("data-reactive-offline",globalThis.navigator?.onLine===!1)};j(),window.addEventListener("online",j),window.addEventListener("offline",j)}function mj(){S=!1}var k="phlex-reactive:latency",x=!1;function Vj(j){if(typeof sessionStorage>"u")return;sessionStorage.setItem(k,String(j))}function Aj(){if(typeof sessionStorage>"u")return;sessionStorage.removeItem(k),x=!1}function Mj(){if(typeof window>"u"||typeof document>"u")return;if(document.querySelector?.('meta[name="phlex-reactive-env"]')?.content!=="development")return;window.PhlexReactive={enableLatencySim:Vj,disableLatencySim:Aj}}function gj(){x=!1}var d="data-reactive-active",A=0;function l(){if(A++,A===1)s("reactive:busy")}function o(){if(A===0)return;if(A--,A===0)s("reactive:idle")}function cj(){return A}function dj(){A=0,(typeof document<"u"?document.documentElement:null)?.removeAttribute?.(d)}function s(j){if(typeof document>"u")return;let Q=document.documentElement;if(typeof Q?.toggleAttribute==="function")Q.toggleAttribute(d,A>0);if(typeof document.dispatchEvent==="function"&&typeof CustomEvent==="function")document.dispatchEvent(new CustomEvent(j,{detail:{count:A}}))}function h(){$j(),Jj(),Gj(),zj(),_j(),Nj(),Mj()}function Bj(j){return j.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}if(typeof window<"u")if(window.Turbo)h();else document.addEventListener("turbo:load",h,{once:!0});var E=!1;function xj(){if(E)return;if(typeof document>"u")return;let j=document.querySelectorAll('[data-controller~="reactive"]');if(!j||j.length===0)return;console.warn("[phlex-reactive] found "+j.length+' element(s) with data-controller="reactive" '+"but the reactive controller never connected. It is loaded but not registered — "+'add `application.register("reactive", ReactiveController)` (importmap) or import it into app/javascript/controllers/ for lazyLoadControllersFrom apps. See the README.')}function lj(){E=!1}function oj(){E=!0}if(typeof window<"u"&&typeof document<"u"){let j=()=>setTimeout(xj,0);if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",j,{once:!0});else j()}var Oj=new Set(["href","src","srcdoc","action","formaction","xlink:href","style"]);function Pj(j){let Q=String(j).toLowerCase();return Q.startsWith("on")||Oj.has(Q)}function Ej(j,Q,X){let[Z,$,J]=Q;j.classList.add(Z,$),X(),requestAnimationFrame(()=>{j.classList.remove($),j.classList.add(J)});let G=!1,K=()=>{if(G)return;G=!0,j.classList.remove(Z,J)};j.addEventListener("animationend",K,{once:!0}),setTimeout(K,350)}var y=Object.freeze({show:(j,Q)=>R(j,!1,Q),hide:(j,Q)=>R(j,!0,Q),toggle:(j,Q)=>R(j,!j.hidden,Q),add_class:(j,Q)=>j.classList.add(...Q.classes??[]),remove_class:(j,Q)=>j.classList.remove(...Q.classes??[]),toggle_class:(j,Q)=>(Q.classes??[]).forEach((X)=>j.classList.toggle(X)),set_attr:(j,Q)=>{if(T(Q.name))j.setAttribute(Q.name,Q.value??"")},remove_attr:(j,Q)=>{if(T(Q.name))j.removeAttribute(Q.name)},toggle_attr:(j,Q)=>{if(!T(Q.name))return;if(j.hasAttribute(Q.name))j.removeAttribute(Q.name);else j.setAttribute(Q.name,"")},focus:(j)=>j.focus?.(),focus_first:(j)=>kj(j)?.focus?.(),text:(j,Q)=>{let X=String(Q.value??"");if(j.textContent!==X)j.textContent=X},dispatch:(j,Q)=>{j.dispatchEvent(new CustomEvent(Q.name,{bubbles:!0,composed:!0,detail:Q.detail??{}}))}});function R(j,Q,X){if(X?.transition)Ej(j,X.transition,()=>j.hidden=Q);else j.hidden=Q}function T(j){if(!Pj(j))return!0;return console.warn(`[phlex-reactive] refused client attr op on ${JSON.stringify(j)} — skipped`),!1}var i=/^#[A-Za-z_][\w-]*$/;function Fj(j){if(typeof j==="string"&&i.test(j))return!0;return console.warn(`[phlex-reactive] refused cross-root mirror target ${JSON.stringify(j)} — skipped`),!1}function Cj(j,Q){let X=j.getAttribute("data-reactive-show-equals");if(X!==null)return Q===X;let Z=j.getAttribute("data-reactive-show-not");if(Z!==null)return Q!==Z;let $=j.getAttribute("data-reactive-show-in");if($!==null){try{let J=JSON.parse($);if(Array.isArray(J))return J.includes(Q)}catch{}return console.warn(`[phlex-reactive] malformed reactive_show in: list ${JSON.stringify($)} — skipped`),null}for(let J of n){let G=j.getAttribute(`data-reactive-show-${J}`);if(G!==null)return a(J,G,Q)}return console.warn("[phlex-reactive] a reactive_show binding declares no predicate — skipped"),null}var n=["gte","gt","lte","lt"];function a(j,Q,X){let Z=Number(Q);if(Number.isNaN(Z))return console.warn(`[phlex-reactive] reactive_show ${j}: needs a numeric literal, got ${JSON.stringify(Q)} — skipped`),null;let $=X==null?"":String(X).trim(),J=$===""?NaN:Number($);if(Number.isNaN(J))return!1;switch(j){case"gte":return J>=Z;case"gt":return J>Z;case"lte":return J<=Z;case"lt":return J<Z;default:return null}}function r(j,Q){if(!j||typeof j!=="object")return null;if(typeof j.equals==="string")return Q===j.equals;if(typeof j.not==="string")return Q!==j.not;if(Array.isArray(j.in))return j.in.includes(Q);for(let X of n)if(X in j)return a(X,j[X],Q);return null}var f="[data-reactive-show-field], [data-reactive-show]";function Rj(j){try{let Q=JSON.parse(j);if(Q&&typeof Q==="object"&&!Array.isArray(Q))return Q}catch{}return console.warn(`[phlex-reactive] malformed compound reactive_show payload ${JSON.stringify(j)} — skipped`),null}function w(j,Q){if(!j||typeof j!=="object"||typeof j.field!=="string")return!1;let X=Q(j.field)??"";return r(j,X)===!0}function t(j,Q){if(!Array.isArray(j)||j.length===0)return null;return j.some((X)=>Array.isArray(X)&&X.length>0&&X.every((Z)=>w(Z,Q)))}function Tj(j,Q){if(!j||typeof j!=="object")return null;let X=j.any;if(Array.isArray(X)&&(X.length===0||Array.isArray(X[0])))return t(X,Q);return Dj(j,Q)}function Dj(j,Q){let X=Array.isArray(j.all)?"all":Array.isArray(j.any)?"any":null;if(!X)return null;let Z=j[X];if(Z.length===0)return null;let $=Z.map((J)=>w(J,Q));return X==="all"?$.every(Boolean):$.some(Boolean)}function Ij(j){if(typeof j==="string"&&i.test(j))return!0;return console.warn(`[phlex-reactive] refused cross-root show target ${JSON.stringify(j)} — skipped`),!1}var Sj='a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';function kj(j){return j.querySelectorAll?.(Sj)?.[0]??null}function e(j){if(Array.isArray(j))return j;if(typeof j!=="string")return[];try{let Q=JSON.parse(j);return Array.isArray(Q)?Q:[]}catch{return[]}}function jj(j,Q){for(let X of j){if(!Array.isArray(X))continue;let[Z,$={}]=X;if(!Object.hasOwn(y,Z)){console.warn(`[phlex-reactive] unknown client op ${JSON.stringify(Z)} — skipped`);continue}for(let J of Q($))y[Z](J,$)}}function wj(j,Q){let X=j.to;if(Q){if(X==="@root")return[Q];if(typeof X!=="string"||X==="")return[];if(j.global)return[...document.querySelectorAll(X)];return[...Q.querySelectorAll(X)]}if(typeof X!=="string"||X===""||X==="@root")return[];return[...document.querySelectorAll(X)]}class sj extends Qj{static values={token:String};#R;#L=new Map;#K=new Map;#e;#B;#T;#x=0;#q=new Map;#O=new WeakMap;#N=new Map;#D=new WeakSet;#Y;#U;#H;#j;#X;#W;#V;connect(){if(E=!0,this.element.id==="")console.warn("[phlex-reactive] a reactive root has no id; its next-action token can't self-match "+"and may fall back to the first token in the response → a silent HTTP 403 on the NEXT action. "+"Put id: on the SAME element as reactive_attrs — use div(**reactive_root) (emits id + token together), "+"or div(id:, **reactive_attrs). The id: must NOT be on a child. See the README.");if(this.element.getAttribute?.("data-reactive-defer-token"))this.#I(),this.#V=()=>this.#I(),this.element.addEventListener?.("turbo:morph-element",this.#V);if(this.#jj()){if(this.#Y=()=>this.#F(),this.element.addEventListener?.("turbo:morph-element",this.#Y),this.#F(),this.element.getAttribute?.("data-reactive-warn-unsaved")==="true")this.#Bj()}if(this.#Oj())this.#j=()=>this.#g(),this.element.addEventListener?.("input",this.#j),this.element.addEventListener?.("change",this.#j),this.element.addEventListener?.("turbo:morph-element",this.#j),this.#g();if(this.#Cj())this.#X=(j)=>{if(j?.type==="input"&&!this.#Tj(j))return;this.#l()},this.element.addEventListener?.("input",this.#X),this.element.addEventListener?.("turbo:morph-element",this.#X),this.#l();if(this.#Rj())this.#W=()=>this.recompute(),this.element.addEventListener?.("turbo:morph-element",this.#W),this.recompute()}#jj(){if((this.element.getAttribute?.("data-action")??"").includes("reactive#trackDirty"))return!0;let j=this.element.querySelectorAll?.('[data-action*="reactive#trackDirty"]')??[];for(let Q of j)if(this.#Q(Q))return!0;return!1}disconnect(){if(this.#qj(),this.#Uj(),this.#xj(),this.#Fj(),this.#Dj(),this.#Ij(),this.#V)this.element.removeEventListener?.("turbo:morph-element",this.#V)}#I(){let j=this.element;if(!j?.id)return;let Q=j.getAttribute?.("data-reactive-defer-token");if(!Q)return;if(j.getAttribute?.("data-reactive-defer-pending")!=="true")return;P(j.id,Q)}dispatch(j){let{action:Q,params:X,debounce:Z,throttle:$,confirm:J,confirmWhen:G,outside:K,window:z,optimistic:Y}=j.params;if(!Q)return;let H=j.params.busy??this.#uj(j.params.loading);if(K&&this.element.contains(j.target))return;let L=j.currentTarget??j.target;if(!z&&!this.#wj(Y,L))j.preventDefault();let W=this.#p(J,G);if(!W)return this.#b(L,Q,X,Z,$,Y,H);Promise.resolve().then(()=>b(W)).catch(()=>!1).then((_)=>{if(_)this.#b(L,Q,X,Z,$,Y,H)})}runOps(j){let{ops:Q,confirm:X,confirmWhen:Z,outside:$,window:J}=j.params;if($&&this.element.contains(j.target))return;if(!J)j.preventDefault();let G=this.#p(X,Z);if(!G)return this.#i(this.#s(Q));Promise.resolve().then(()=>b(G)).catch(()=>!1).then((K)=>{if(K)this.#i(this.#s(Q))})}trackDirty(){this.#F()}recompute(j){if(j&&this.#D.has(j))return;let Q=this.#Xj(),X=Q.map(([q])=>q),Z=this.element.getAttribute?.("data-reactive-scope")||null,$=(q)=>Z&&!q.includes("[")?`${Z}[${q}]`:q,J=this.#_(),G=new Map,K=(q)=>{if(G.has(q))return G.get(q);let U=null;for(let M of this.element.querySelectorAll(`[name="${$(q)}"]`))if(J(M)){U=M;break}return G.set(q,U),U};for(let q of X)this.#k(q,K(q)?.value??"");let z=this.element.getAttribute("data-reactive-compute-reducer-param"),Y=z?Xj(z):null;if(!Y){this.#w({},K);return}let H=this.#Qj("data-reactive-compute-outputs-param"),L={};for(let[q,U]of Q){let M=K(q);if(U==="string")L[q]=M?.value??"";else{let N=Number(M?.value);L[q]=Number.isFinite(N)?N:0}}let W=Y(L,{changed:this.#Zj(j,X,Z)})||{},_=[];for(let q of H){if(!(q in W))continue;let U=K(q);if(!U)continue;if(String(W[q])===U.value)continue;U.value=W[q],_.push(U)}for(let q of Object.keys(W)){let U=W[q];if(U===void 0||U===null)continue;this.#k(q,U)}this.#w(W,K);for(let q of _){let U=new Event("input",{bubbles:!0});this.#D.add(U),q.dispatchEvent(U)}}listnavNext(j){this.#S(j,1)}listnavPrev(j){this.#S(j,-1)}listnavPick(j){let Q=this.#P(j),X=Q.findIndex((Z)=>Z.hasAttribute("data-reactive-highlighted"));if(X<0)return;j.preventDefault(),Q[X].click()}listnavClose(j){for(let Q of this.#P(j))Q.removeAttribute("data-reactive-highlighted")}#S(j,Q){let X=this.#P(j);if(!X.length)return;j.preventDefault();let Z=X.findIndex((G)=>G.hasAttribute("data-reactive-highlighted")),$=Z<0?Q>0?0:X.length-1:(Z+Q+X.length)%X.length;for(let G of X)G.removeAttribute("data-reactive-highlighted");let J=X[$];J.setAttribute("data-reactive-highlighted","true"),J.scrollIntoView?.({block:"nearest"})}#P(j){let X=(j?.currentTarget??j?.target??this.element).getAttribute?.("data-reactive-listnav-option-param")??this.element.getAttribute("data-reactive-listnav-option-param");if(!X)return[];let Z=this.#_();return Array.from(this.element.querySelectorAll(X)).filter(($)=>!$.hidden&&Z($))}#Qj(j){let Q=this.element.getAttribute(j);if(!Q)return[];try{let X=JSON.parse(Q);return Array.isArray(X)?X:[]}catch{return[]}}#Xj(){let j=this.element.getAttribute("data-reactive-compute-inputs-param");if(!j)return[];try{let Q=JSON.parse(j);if(Array.isArray(Q))return Q.map((X)=>[X,"number"]);if(Q&&typeof Q==="object")return Object.entries(Q);return[]}catch{return[]}}#Zj(j,Q,X){let Z=j?.target;if(!Z?.name||typeof Z.closest!=="function")return null;let $=this.#$j(Z.name,X);if(!Q.includes($))return null;return this.#Q(Z)?$:null}#$j(j,Q){if(!Q)return j;let X=`${Q}[`;return j.startsWith(X)&&j.endsWith("]")?j.slice(X.length,-1):j}#k(j,Q){let X=String(Q);for(let Z of this.#Jj(j)){if(Z.textContent===X)continue;Z.textContent=X}}#Jj(j){let Q=this.element.querySelectorAll(`[data-reactive-text="${j}"]`);return Array.from(Q).filter((X)=>this.#Q(X))}#w(j,Q){let X=this.#Gj();for(let[Z,$]of Object.entries(X)){let J=Z in j?j[Z]:Q(Z)?.value;if(J===void 0||J===null)continue;let G=String(J);for(let K of Array.isArray($)?$:[$]){if(!Fj(K))continue;for(let z of document.querySelectorAll(K)){if(z.textContent===G)continue;z.textContent=G}}}}#Gj(){let j=this.element.getAttribute("data-reactive-compute-mirror-param");if(!j)return{};try{let Q=JSON.parse(j);return Q&&typeof Q==="object"&&!Array.isArray(Q)?Q:{}}catch{return{}}}#b(j,Q,X,Z,$,J,G){if(this.#M("reactive:before-dispatch",{action:Q,params:this.#o(X),element:this.element},{cancelable:!0}).defaultPrevented)return;let z=Number(Z)||0;if(z>0)return this.#Kj(j,z,Q,X,J,G);let Y=Number($)||0;if(Y>0)return this.#Yj(j,Y,Q,X,J,G);return this.#A(Q,X,J,j,G)}#A(j,Q,X,Z,$){let J=this.#bj(X,Z),G=this.#vj(j,Z,$),K=this.#v()?this.#zj(X,Z):null;return this.queue=(this.queue??Promise.resolve()).then(()=>this.#Vj(j,Q,J,G,K)),this.queue}#zj(j,Q){if(!j?.hide)return null;let X=this.#r(j,Q);if(!X.length)return null;return()=>{let Z=X.filter(($)=>$.isConnected&&!$.hidden);if(!Z.length)return;console.warn("[phlex-reactive] optimistic: { hide: true } was undone by the reply's re-render — "+"the element is visible again. For an instant delete, return reply.remove so the server removes it; otherwise the hide only flashes.",Z)}}#Kj(j,Q,X,Z,$,J){this.#E(j);let G=()=>{this.#E(j),this.#A(X,Z,$,j,J)},K=setTimeout(G,Q);j?.addEventListener?.("blur",G,{once:!0}),this.#L.set(j,{timer:K,flush:G})}#E(j){let Q=this.#L.get(j);if(!Q)return;clearTimeout(Q.timer),j?.removeEventListener?.("blur",Q.flush),this.#L.delete(j)}#qj(){for(let j of[...this.#L.keys()])this.#E(j)}#Yj(j,Q,X,Z,$,J){let G=this.#K.get(j)??new Map;if(G.has(X))return;let K=setTimeout(()=>{if(G.delete(X),G.size===0)this.#K.delete(j)},Q);return G.set(X,K),this.#K.set(j,G),this.#A(X,Z,$,j,J)}#Uj(){for(let j of this.#K.values())for(let Q of j.values())clearTimeout(Q);this.#K.clear()}#M(j,Q,{cancelable:X=!1}={}){let Z=new CustomEvent(j,{bubbles:!0,composed:!0,cancelable:X,detail:Q});return(this.element.isConnected?this.element:document).dispatchEvent(Z),Z}#Z(j,Q,X,Z){let $=()=>{if(!this.element.isConnected){console.warn("[phlex-reactive] retry() ignored — the reactive root left the DOM");return}return this.#A(j,Q)};this.#M("reactive:error",{action:j,params:X,...Z,retry:$})}#$(j){if(this.element?.isConnected===!1)return;this.element?.setAttribute?.("data-reactive-error",j)}#Hj(){this.element?.removeAttribute?.("data-reactive-error")}#Wj(){let j=document.querySelector("[data-reactive-error-flash]");if(!j?.content)return;let Q=j.getAttribute("data-reactive-error-flash")||"flash",X=document.getElementById(Q);if(!X)return;X.appendChild(j.content.cloneNode(!0))}#_j(){if(typeof sessionStorage>"u")return Promise.resolve();let j=Number(sessionStorage.getItem(k));if(!Number.isFinite(j)||j<=0)return Promise.resolve();if(!x)x=!0,console.warn(`[phlex-reactive] latency simulator ACTIVE — every action is delayed by ${j}ms. Call PhlexReactive.disableLatencySim() (or clear sessionStorage) to turn it off.`);return new Promise((Q)=>setTimeout(Q,j))}#v(){return this.element?.getAttribute?.("data-reactive-debug")==="true"}#h(){return typeof performance<"u"&&typeof performance.now==="function"?performance.now():Date.now()}#Lj(j){if(!j)return[];let Q=[],X=/<turbo-stream\b([^>]*)>/g,Z;while((Z=X.exec(j))!==null){let $=Z[1],J=$.match(/\baction="([^"]*)"/)?.[1]??"?",G=$.match(/\btarget="([^"]*)"/)?.[1];Q.push(G?`${J} → #${G}`:J)}return Q}#Nj(j){let{action:Q,status:X,ms:Z}=j,J=`reactive ${this.element?.id?`#${this.element.id} `:""}${Q} → ${X??"—"} (${Math.round(Z)}ms)`;if(console.groupCollapsed(J),console.log(`params: [${j.paramNames.join(", ")}] + collected: [${j.fieldNames.join(", ")}]`),console.log(`encoding: ${j.encoding}`),j.streams.length)console.log(`streams: ${j.streams.join(" ")}`);console.log(`token: ${j.tokenRefreshed?"refreshed ✓":"unchanged"}`),console.groupEnd()}async#Vj(j,Q,X,Z,$){let{fields:J,files:G}=this.#u(),K=this.#o(Q),z={...J,...K},Y=this.#J,H=G.length>0,L=H?this.#Sj(Y,j,z,G):JSON.stringify({token:Y,act:j,params:z}),W=this.#v()?{action:j,paramNames:Object.keys(K),fieldNames:Object.keys(J),encoding:H?"multipart":"json",status:null,streams:[],tokenRefreshed:!1,started:this.#h()}:null;await this.#_j();try{if(navigator.onLine===!1){this.#G(X),this.#$("offline"),this.#Z(j,Q,z,{kind:"offline"});return}let _;try{let N={Accept:"text/vnd.turbo-stream.html","X-CSRF-Token":this.#cj()};if(!H)N["Content-Type"]="application/json";let B=this.#dj();if(B)N["X-Pgbus-Connection"]=B;_=await fetch(this.#mj(),{method:"POST",headers:N,body:L,credentials:"same-origin",signal:AbortSignal.timeout(this.#gj())})}catch(N){if(console.error("[phlex-reactive] action error",N),this.#G(X),N?.name==="TimeoutError"||N?.name==="AbortError"){this.#$("timeout"),this.#Z(j,Q,z,{kind:"timeout"});return}this.#Wj(),this.#$("network"),this.#Z(j,Q,z,{kind:"network"});return}if(W)W.status=_.status;if(_.redirected){console.error("[phlex-reactive] action was redirected (auth/CSRF?) — no update applied"),this.#G(X),this.#$("redirected"),this.#Z(j,Q,z,{kind:"redirected",status:_.status});return}if(!_.ok){let N=await _.text();if(console.error(`[phlex-reactive] action failed: HTTP ${_.status}`,N),this.#G(X),(_.headers.get("Content-Type")||"").includes("turbo-stream")){let B=this.#f(N);if(this.#J=B??this.#J,W)this.#y(W,N,B);window.Turbo.renderStreamMessage(N)}this.#$("http"),this.#Z(j,Q,z,{kind:"http",status:_.status,body:N});return}let q=_.headers.get("Content-Type")||"";if(!q.includes("turbo-stream")){console.error(`[phlex-reactive] expected a turbo-stream, got "${q}" — no update applied`),this.#G(X),this.#$("content-type"),this.#Z(j,Q,z,{kind:"content-type",status:_.status});return}let U=await _.text(),M=this.#f(U);if(this.#J=M??this.#J,W)this.#y(W,U,M);if(window.Turbo.renderStreamMessage(U),$)queueMicrotask($);this.#Hj(),this.#M("reactive:applied",{action:j,params:z,html:U})}catch(_){console.error("[phlex-reactive] action error",_),this.#G(X),this.#M("reactive:error",{action:j,params:z,kind:"apply"})}finally{if(Z?.(),W)this.#Nj({...W,ms:this.#h()-W.started})}}#y(j,Q,X){j.streams=this.#Lj(Q),j.tokenRefreshed=X!=null}get#J(){return this.#R??this.tokenValue}set#J(j){this.#R=j}#f(j){let Q=this.element.id;if(!Q)return j.match(/data-reactive-token-value="([^"]+)"/)?.[1];let{token:X,self:Z}=this.#Aj(Q),$=j.match(X);if($)return $[1];let J=j.match(Z);if(J)return J[1].match(/data-reactive-token-value="([^"]+)"/)?.[1];return}#Aj(j){let Q=this.#T;if(Q&&Q.id===j)return Q;let X=Bj(j);return this.#T={id:j,token:new RegExp(`<turbo-stream\\b[^>]*\\baction="reactive:token"[^>]*\\btarget="${X}"[^>]*\\bdata-reactive-token-value="([^"]+)"`),self:new RegExp(`<turbo-stream\\b[^>]*\\baction="(?:replace|update)"[^>]*\\btarget="${X}"[^>]*>([\\s\\S]*?)</turbo-stream>`)}}#Q(j){return j.closest('[data-controller~="reactive"]')===this.element}#_(){if(this.element.querySelectorAll('[data-controller~="reactive"]').length===0)return()=>!0;return(Q)=>this.#Q(Q)}#p(j,Q){if(j)return j;if(!Q)return null;let X=Q;if(typeof Q==="string")try{X=JSON.parse(Q)}catch{return console.warn(`[phlex-reactive] malformed conditional confirm payload ${JSON.stringify(Q)} — skipped`),null}if(!X||typeof X!=="object")return null;let{fields:Z}=this.#u(),$=(G)=>Z[G],J;if(typeof X.predicate==="string"){let G=Zj(X.predicate);if(!G)return console.warn(`[phlex-reactive] confirm predicate "${X.predicate}" is not registered — proceeding without a dialog (register it with setConfirmPredicate)`),null;J=!!G(Z)}else J=t(X.groups?.any,$)===!0;return J?X.message:null}#u(){let j={},Q=[],X=this.#_();return this.element.querySelectorAll("input[name], select[name], textarea[name]").forEach((Z)=>{if(!X(Z))return;if(Z.type==="file")for(let $ of Z.files??[])Q.push({name:Z.name,file:$,multiple:Z.multiple});else if(Z.type==="checkbox")j[Z.name]=Z.checked;else if(Z.type==="radio"){if(Z.checked)j[Z.name]=Z.value}else j[Z.name]=Z.value}),this.element.querySelectorAll("[name]:is(lexxy-editor, trix-editor, [contenteditable=''], [contenteditable=true], [contenteditable=plaintext-only])").forEach((Z)=>{if(!X(Z))return;let $=Z.getAttribute("name");if(!$)return;let J=j[$];if(J==null||J==="")j[$]=Z.value??Z.textContent??Z.innerHTML??""}),{fields:j,files:Q}}#F(){if(typeof this.element?.querySelectorAll!=="function")return;let j=0;if(this.element.querySelectorAll("input[name], select[name], textarea[name]").forEach((Q)=>{if(!this.#Q(Q))return;if(Q.type==="file")return;if(this.#Mj(Q))Q.setAttribute("data-reactive-dirty","true"),j++;else Q.removeAttribute("data-reactive-dirty")}),j>0)this.element.setAttribute("data-reactive-dirty",String(j));else this.element.removeAttribute("data-reactive-dirty")}#Mj(j){if(j.type==="checkbox"||j.type==="radio")return j.checked!==j.defaultChecked;if(j.tag==="select"||j.options)return Array.from(j.options??[]).some((Q)=>Q.selected!==Q.defaultSelected);return j.value!==j.defaultValue}#m(){let j=this.element.getAttribute?.("data-reactive-dirty"),Q=Number(j);return Number.isFinite(Q)&&Q>0?Q:0}#Bj(){if(typeof window>"u"||typeof window.addEventListener!=="function")return;this.#U=(j)=>{if(this.#m()===0)return;return j.preventDefault(),j.returnValue="You have unsaved changes.",j.returnValue},this.#H=(j)=>{if(this.#m()===0)return;if(!(typeof window.confirm==="function"?window.confirm("You have unsaved changes. Leave anyway?"):!0))j.preventDefault?.()},window.addEventListener("beforeunload",this.#U),window.addEventListener("turbo:before-visit",this.#H)}#xj(){if(this.#Y)this.element.removeEventListener?.("turbo:morph-element",this.#Y),this.#Y=void 0;if(typeof window<"u"&&typeof window.removeEventListener==="function"){if(this.#U)window.removeEventListener("beforeunload",this.#U);if(this.#H)window.removeEventListener("turbo:before-visit",this.#H)}this.#U=void 0,this.#H=void 0}#Oj(){if(this.element.getAttribute?.("data-reactive-show-targets"))return!0;let j=this.element.querySelectorAll?.(f)??[];for(let Q of j)if(this.#Q(Q))return!0;return!1}#g(){if(typeof this.element?.querySelectorAll!=="function")return;let j=this.#_(),Q=this.element.getAttribute?.("data-reactive-scope")||null,X=new Map,Z=($)=>{if(!X.has($))X.set($,this.#d($,j,Q));return X.get($)};for(let $ of this.element.querySelectorAll(f)){if(!j($))continue;let J=$.getAttribute("data-reactive-show");if(J!==null){let Y=Tj(Rj(J),Z);if(Y!==null)this.#c($,Y,j,Q);continue}let G=$.getAttribute("data-reactive-show-field");if(!G)continue;let K=Z(G);if(K===null)continue;let z=Cj($,K);if(z===null)continue;this.#c($,z,j,Q)}this.#Pj(j,X,Q)}#c(j,Q,X,Z){if(j.hidden=!Q,j.getAttribute("data-reactive-show-disable")!=="true")return;if(typeof j.querySelectorAll!=="function")return;for(let $ of j.querySelectorAll("input[name], select[name], textarea[name]"))if(X($))$.disabled=!Q;if(j.name&&X(j))j.disabled=!Q}#Pj(j,Q,X){let Z=this.#Ej();for(let[$,J]of Object.entries(Z)){if(!J||typeof J!=="object"||Array.isArray(J))continue;if(!Q.has($))Q.set($,this.#d($,j,X));let G=Q.get($);if(G===null)continue;let K=()=>G;for(let[z,Y]of Object.entries(J)){if(!Ij(z))continue;let H;if(Array.isArray(Y)){if(Y.length===0){console.warn(`[phlex-reactive] malformed reactive_show_targets group for ${z} — skipped`);continue}H=Y.every((L)=>w(L,K))}else{let L=r(Y,G);if(L===null){console.warn(`[phlex-reactive] malformed reactive_show_targets predicate for ${z} — skipped`);continue}H=L}for(let L of document.querySelectorAll(z))L.hidden=!H}}}#Ej(){let j=this.element.getAttribute?.("data-reactive-show-targets");if(!j)return{};try{let Q=JSON.parse(j);if(Q&&typeof Q==="object"&&!Array.isArray(Q))return Q}catch{}return console.warn("[phlex-reactive] malformed data-reactive-show-targets — ignored. "+"Did two reactive_show_targets calls collide on one root? Declare every field in ONE call: reactive_show_targets(mode: { ... }, kind: { ... })"),{}}#d(j,Q,X){let Z=X&&!j.includes("[")?`${X}[${j}]`:j,$=!1,J=null;for(let G of this.element.querySelectorAll(`[name="${Z}"]`)){if(!Q(G))continue;if(G.type==="checkbox")return G.checked?"true":"false";if(G.type==="radio"){if(G.checked)return G.value??"";$=!0;continue}J??=G}if(J)return J.value??"";return $?"":null}#Fj(){if(!this.#j)return;this.element.removeEventListener?.("input",this.#j),this.element.removeEventListener?.("change",this.#j),this.element.removeEventListener?.("turbo:morph-element",this.#j),this.#j=void 0}#Cj(){return!!(this.element.getAttribute?.("data-reactive-filter-input")&&this.element.getAttribute?.("data-reactive-filter-option"))}#Rj(){return this.element.getAttribute?.("data-reactive-compute-seed")==="true"}#Tj(j){let Q=this.element.getAttribute("data-reactive-filter-input");return!!Q&&typeof j.target?.matches==="function"&&j.target.matches(Q)}#l(){if(typeof this.element?.querySelectorAll!=="function")return;let j=this.element.getAttribute("data-reactive-filter-input"),Q=this.element.getAttribute("data-reactive-filter-option");if(!j||!Q)return;let X=this.#_(),Z=[...this.element.querySelectorAll(j)].find(X);if(!Z)return;let $=(Z.value??"").trim().toLowerCase(),J=0;for(let z of this.element.querySelectorAll(Q)){if(!X(z))continue;let Y=(z.getAttribute("data-reactive-filter-text")??z.textContent??"").toLowerCase(),H=$!==""&&!Y.includes($);if(z.hidden=H,H)z.removeAttribute("data-reactive-highlighted");else J++}let G=this.element.getAttribute("data-reactive-filter-group");if(G)for(let z of this.element.querySelectorAll(G)){if(!X(z))continue;let Y=[...z.querySelectorAll(Q)].filter(X);if(Y.length===0)continue;z.hidden=Y.every((H)=>H.hidden)}let K=this.element.getAttribute("data-reactive-filter-empty");if(K){for(let z of this.element.querySelectorAll(K))if(X(z))z.hidden=J>0}}#Dj(){if(!this.#X)return;this.element.removeEventListener?.("input",this.#X),this.element.removeEventListener?.("turbo:morph-element",this.#X),this.#X=void 0}#Ij(){if(!this.#W)return;this.element.removeEventListener?.("turbo:morph-element",this.#W),this.#W=void 0}#Sj(j,Q,X,Z){let $=new FormData;$.append("token",j),$.append("act",Q);for(let[G,K]of Object.entries(X))this.#C($,`params[${G}]`,K);let J=this.#kj(Z);for(let{name:G,file:K,multiple:z}of Z){let H=z||J.has(G)?`params[${G}][]`:`params[${G}]`;$.append(H,K,K.name)}return $}#C(j,Q,X){if(X==null)j.append(Q,"");else if(Array.isArray(X))X.forEach((Z,$)=>this.#C(j,`${Q}[${$}]`,Z));else if(typeof X==="object")for(let[Z,$]of Object.entries(X))this.#C(j,`${Q}[${Z}]`,$);else j.append(Q,String(X))}#kj(j){let Q=new Map;for(let{name:X}of j)Q.set(X,(Q.get(X)??0)+1);return new Set([...Q].filter(([,X])=>X>1).map(([X])=>X))}#o(j){if(!j)return{};try{return typeof j==="string"?JSON.parse(j):j}catch{return{}}}#s(j){return e(j)}#i(j){jj(j,(Q)=>this.#n(Q))}#n(j){let Q=j.to;if(Q==="@root")return[this.element];if(typeof Q!=="string"||Q==="")return[];if(j.global)return[...document.querySelectorAll(Q)];return[...this.element.querySelectorAll(Q)].filter((X)=>this.#Q(X))}#wj(j,Q){if(j?.checked!=="keep")return!1;let X=Q?.type;return X==="checkbox"||X==="radio"}#bj(j,Q){if(!j)return null;let X=this.#a(j,Q,!0);return X.length?X:null}#G(j){if(!j)return;if(!this.element.isConnected)return;for(let Q of j)Q()}#vj(j,Q,X){this.#yj(j,Q);let Z=X?this.#a(X,Q,!1):[];l();let $=!1;return()=>{if($)return;$=!0,this.#fj(j,Q),o();for(let J of Z)J()}}#a(j,Q,X){let Z=[];for(let $ of this.#r(j,Q)){if(j.add_class){let J=j.add_class.filter((G)=>!$.classList.contains(G));if($.classList.add(...J),J.length)Z.push(()=>$.classList.remove(...J))}if(j.remove_class){let J=j.remove_class.filter((G)=>$.classList.contains(G));if($.classList.remove(...J),J.length)Z.push(()=>$.classList.add(...J))}if(j.toggle_class)j.toggle_class.forEach((J)=>$.classList.toggle(J)),Z.push(()=>j.toggle_class.forEach((J)=>$.classList.toggle(J)));if(j.hide)$.hidden=!0,Z.push(()=>$.hidden=!1);if(j.show)$.hidden=!1,Z.push(()=>$.hidden=!0)}if(Q&&(j.disable||j.text!=null))Z.push(this.#hj(j,Q));if(X&&j.checked==="keep"&&Q&&"checked"in Q){let $=Q.checked;Z.push(()=>Q.checked=!$)}return Z}#r(j,Q){if(j.to==null)return Q?[Q]:[];return this.#n({to:j.to})}#hj(j,Q){let X=this.#N.get(Q);if(X)X.count++;else this.#N.set(Q,{count:1,disabled:Q.disabled,html:Q.innerHTML,hadText:j.text!=null,swappedTo:j.text});if(j.disable)Q.disabled=!0;if(j.text!=null)Q.innerHTML=j.text;return()=>this.#pj(Q,j)}#yj(j,Q){if(this.#z(Q,j,1),this.#z(this.element,j,1),this.#q.set(j,(this.#q.get(j)??0)+1),this.#x++===0)this.element.setAttribute("aria-busy","true");for(let X of this.#t(j))this.#z(X,j,1)}#fj(j,Q){this.#z(Q,j,-1),this.#z(this.element,j,-1);let X=(this.#q.get(j)??1)-1;if(X<=0)this.#q.delete(j);else this.#q.set(j,X);if(--this.#x<=0)this.#x=0,this.element.removeAttribute("aria-busy");for(let Z of this.#t(j))this.#z(Z,j,-1)}#z(j,Q,X){if(!j||typeof j.getAttribute!=="function")return;let Z=this.#O.get(j)??new Map,$=(Z.get(Q)??0)+X;if($<=0)Z.delete(Q);else Z.set(Q,$);if(Z.size===0){this.#O.delete(j),j.removeAttribute("data-reactive-busy");return}this.#O.set(j,Z),j.setAttribute("data-reactive-busy",[...Z.keys()].join(" "))}#t(j){return[...this.element.querySelectorAll?.("[data-reactive-busy-on]")??[]].filter((X)=>X.getAttribute("data-reactive-busy-on")===j&&this.#Q(X))}#pj(j,Q){let X=this.#N.get(j);if(!X)return;if(--X.count>0)return;if(this.#N.delete(j),!j.isConnected)return;if(Q.disable)j.disabled=X.disabled;if(X.hadText&&j.innerHTML===X.swappedTo)j.innerHTML=X.html}#uj(j){if(!j||typeof j!=="object")return null;let{class:Q,...X}=j;return Q==null?j:{...X,add_class:Q}}#mj(){return this.#e??=document.querySelector('meta[name="phlex-reactive-action-path"]')?.content||"/reactive/actions"}#gj(){if(this.#B!=null)return this.#B;let j=document.querySelector('meta[name="phlex-reactive-timeout"]')?.content,Q=Number(j);return this.#B=Number.isFinite(Q)&&Q>0?Q:30000}#cj(){return document.querySelector('meta[name="csrf-token"]')?.content??""}#dj(){return document.querySelector("pgbus-stream-source[connection-id]")?.getAttribute("connection-id")||document.querySelector('meta[name="pgbus-connection-id"]')?.content||null}}export{fj as resetReactiveDefers,dj as resetReactiveActivity,$j as registerReactiveVisit,Jj as registerReactiveToken,Nj as registerReactiveOffline,Gj as registerReactiveJs,_j as registerReactiveDismiss,zj as registerReactiveDefer,h as registerReactiveActions,cj as reactiveActivityCount,pj as pendingDeferVia,o as exitReactiveActivity,Bj as escapeRegExp,l as enterReactiveActivity,Vj as enableLatencySim,Aj as disableLatencySim,sj as default,xj as checkReactiveRegistration,lj as __resetReactiveRegistrationForTest,mj as __resetReactiveOfflineForTest,gj as __resetReactiveLatencyForTest,uj as __resetReactiveDismissForTest,oj as __markReactiveConnectedForTest,k as LATENCY_KEY,d as ACTIVE_ATTR};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=E7BCDAE133AC4CB864756E2164756E21
|
|
4
4
|
//# sourceMappingURL=reactive_controller.min.js.map
|