phlex-reactive 0.11.0 → 0.11.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4b92414ce55aa10f4ac2fb223ba0cb689928ca773750dd14c7ef80284156f255
4
- data.tar.gz: bd12836456aa3f2010f8ea86c0f5c3a6a54f7dabfd04f0f5ceb28b6466fb28e6
3
+ metadata.gz: ac1f001e762b40825811633d86a7cb9e050b85d32a7f7c11a44196b48f486af6
4
+ data.tar.gz: a969a678bf9f0d293b953529f9c31eb4871263dc391477e4301d3fedd5cec6fb
5
5
  SHA512:
6
- metadata.gz: 2608d8963434dd4d8a5e015f12e17d54ba7e3c62fdbfab0dca5ffb31c8fd75786eb6467dc864143a2635fa51e1ec574f1ad45a1d804800cb553dcce0fd82f18d
7
- data.tar.gz: e5ad1eeeaab37b4f69fe1ab5368fced9f427bbe56ead3ef24f63c950ded96d755d14a6f2dc9b6dd5f3a8af7fe3d96cef2eb6112eb623a5513ebba322df823cfd
6
+ metadata.gz: 8378be9d18bd169e2f78dfbd5171931e952ecbb875c74df0ba5328518ea0a74c641e7840807a7d6eb216d647ecd0daa6f32cdf70468d049e6ed47fa2d0169903
7
+ data.tar.gz: 7ec7fb3754b137191feb9d38dca9ef9b14b6993da9909dd10ea0f25b661660259c3839b4fa011982dec206d687b765b0a70e7d762ca62c09ed22cbf7f055dba0
data/CHANGELOG.md CHANGED
@@ -6,8 +6,66 @@ 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
+ - **A freshly-rendered `reactive_compute` root now self-seeds its derived fields on connect (#199).**
51
+ Before, a compute root computed nothing until the first user `input` — a fresh render
52
+ (or a server validation-error re-render that replaced the body) left every derived
53
+ output and cross-root `mirror:` node blank until you typed. Apps worked around it by
54
+ dispatching a synthetic seed `input` on connect, but the compute root is a distinct
55
+ Stimulus controller that may connect a frame later, so the seed raced its own wiring —
56
+ the reported symptom being a PARTIAL apply (an early output painted; a later output
57
+ and the mirror stayed blank). Now `connect()` runs ONE full `recompute()` after the
58
+ controller is fully connected, so the whole single-pass write set (batch outputs →
59
+ paint text sinks + cross-root mirrors → dispatch) runs synchronously and every
60
+ declared output, text sink, and mirror paints from a single reducer result — no user
61
+ interaction, no round trip. It re-seeds on `turbo:morph-element` (the show/filter/dirty
62
+ precedent) and is idempotent (change-guarded writes make a re-seed a no-op, so an app
63
+ still dispatching a synthetic input is harmless). The seed is gated on a new
64
+ `data-reactive-compute-seed` marker emitted by `reactive_root(compute:)`; a non-compute
65
+ root pays one attribute read and never seeds. The seed passes no event, so
66
+ `meta.changed` is `null` — the correct "no field edited yet" semantics, so a convergent
67
+ reducer's default branch computes the full settled set.
68
+
11
69
  - **Scoped form fields no longer silently drop their params — the #67 footgun is fixed (#184).**
12
70
  Under `reactive_scope`, `reactive_field(:date)` now emits the scoped wire name
13
71
  (`name="invoice[date]"`), so the POST arrives bracketed — and the endpoint unwraps
@@ -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") pendingDefers.delete(targetId)
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
- pendingDefers.set(targetId, entry)
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
- pendingDefers.set(targetId, { via: "stream" })
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
- pendingDefers.delete(targetId)
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
- pendingDefers.delete(targetId)
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
- pendingDefers.delete(targetId)
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()
@@ -1031,6 +1131,9 @@ export default class extends Controller {
1031
1131
  // Option filtering (issue #163): the ONE delegated sync handler shared by the
1032
1132
  // root's input/turbo:morph-element listeners, held for teardown.
1033
1133
  #boundSyncFilter
1134
+ // Connect-time compute seed (issue #199): the bound re-seed attached to
1135
+ // turbo:morph-element so an in-place morph re-runs the compute, held for teardown.
1136
+ #boundSeedCompute
1034
1137
  // Lazy initial mount (issue #165): the bound re-probe attached to
1035
1138
  // turbo:morph-element so a Turbo page-refresh morph re-fires the defer fetch.
1036
1139
  #boundProbeLazyDefer
@@ -1136,6 +1239,32 @@ export default class extends Controller {
1136
1239
  this.element.addEventListener?.("turbo:morph-element", this.#boundSyncFilter)
1137
1240
  this.#syncFilter()
1138
1241
  }
1242
+
1243
+ // Connect-time compute seed (issue #199) — ONLY when the root carries a
1244
+ // reactive_compute binding that opts in (data-reactive-compute-seed). A
1245
+ // freshly-rendered compute root (a first paint, or a server validation-error
1246
+ // re-render that replaced the body) computed NOTHING until the first user
1247
+ // `input`; apps worked around it by dispatching a synthetic seed `input` on
1248
+ // connect, but the compute root is a distinct Stimulus controller that may
1249
+ // connect a frame later, so the seed raced its own wiring — the reported
1250
+ // symptom being a PARTIAL apply (an early output paints; a later output + the
1251
+ // mirror stay blank). Running ONE recompute() HERE — after Stimulus has fully
1252
+ // connected the controller and wired the input->recompute delegation — runs
1253
+ // the whole single-pass write set (issue #183) synchronously, so every
1254
+ // declared output, text sink, and cross-root mirror paints from one reducer
1255
+ // result. It is client-only (recompute never enqueues a round trip) and
1256
+ // idempotent (change-guarded writes make a re-seed a no-op — an app still
1257
+ // dispatching a synthetic input is harmless). A plain replace re-connects and
1258
+ // re-seeds; an in-place morph keeps the element CONNECTED and fires no
1259
+ // Stimulus lifecycle, so ALSO re-seed on turbo:morph-element (the show/filter/
1260
+ // dirty precedent). No event is passed, so meta.changed is null — the correct
1261
+ // "no field edited yet" seed semantics; a convergent reducer's default branch
1262
+ // computes the full settled set (see compute.js CONVERGENCE REQUIREMENT).
1263
+ if (this.#computeSeedEnabled()) {
1264
+ this.#boundSeedCompute = () => this.recompute()
1265
+ this.element.addEventListener?.("turbo:morph-element", this.#boundSeedCompute)
1266
+ this.recompute()
1267
+ }
1139
1268
  }
1140
1269
 
1141
1270
  // Whether this root opts into dirty tracking (issue #103): track_dirty: puts the
@@ -1162,6 +1291,7 @@ export default class extends Controller {
1162
1291
  this.#teardownDirtyTracking()
1163
1292
  this.#teardownShowSync()
1164
1293
  this.#teardownFilterSync()
1294
+ this.#teardownComputeSeed()
1165
1295
  if (this.#boundProbeLazyDefer) {
1166
1296
  this.element.removeEventListener?.("turbo:morph-element", this.#boundProbeLazyDefer)
1167
1297
  }
@@ -2720,6 +2850,14 @@ export default class extends Controller {
2720
2850
  )
2721
2851
  }
2722
2852
 
2853
+ // Whether this root opts into the connect-time compute seed (issue #199).
2854
+ // reactive_compute's root binding emits data-reactive-compute-seed="true"; a
2855
+ // root without a compute binding (or with the seed opted out) pays one
2856
+ // attribute read and never seeds. A quick read, evaluated once per connect.
2857
+ #computeSeedEnabled() {
2858
+ return this.element.getAttribute?.("data-reactive-compute-seed") === "true"
2859
+ }
2860
+
2723
2861
  // Whether a delegated input event came from the NAMED filter input (issue
2724
2862
  // #163). Anything else — another field's keystroke, a target without
2725
2863
  // matches() — skips the filter pass (the morph re-sync path bypasses this).
@@ -2788,6 +2926,15 @@ export default class extends Controller {
2788
2926
  this.#boundSyncFilter = undefined
2789
2927
  }
2790
2928
 
2929
+ // Remove the compute seed morph listener on disconnect (issue #199), so a
2930
+ // stray turbo:morph-element after the element leaves the DOM never re-seeds
2931
+ // against a detached root.
2932
+ #teardownComputeSeed() {
2933
+ if (!this.#boundSeedCompute) return
2934
+ this.element.removeEventListener?.("turbo:morph-element", this.#boundSeedCompute)
2935
+ this.#boundSeedCompute = undefined
2936
+ }
2937
+
2791
2938
  // Build the multipart body (issue #34). `token`/`act` are flat fields the
2792
2939
  // endpoint reads from params[:token]/params[:act]; scalar params nest under
2793
2940
  // params[<key>] (Rails parses the bracket into params[:params]); each file is
@@ -2946,12 +3093,19 @@ export default class extends Controller {
2946
3093
  #applyBusy(action, trigger, busy) {
2947
3094
  this.#markBusy(action, trigger)
2948
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()
2949
3102
 
2950
3103
  let settled = false
2951
3104
  return () => {
2952
3105
  if (settled) return // one settle per enqueue, even if called twice
2953
3106
  settled = true
2954
3107
  this.#unmarkBusy(action, trigger)
3108
+ exitReactiveActivity()
2955
3109
  // Busy reverts on SETTLE regardless of outcome, guarded per element.
2956
3110
  for (const op of undo) op()
2957
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),u(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),u(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 u(j){j.setAttribute("data-reactive-defer-pending","true"),j.setAttribute("aria-busy","true")}function p(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;p(Q),Q.removeAttribute("data-reactive-error")}function P(j,Q,X){N.delete(j);let Z=document.getElementById(j);if(!Z)return;p(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(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 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)=>E(j,!1,Q),hide:(j,Q)=>E(j,!0,Q),toggle:(j,Q)=>E(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)=>Ej(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 E(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 Cj='a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';function Ej(j){return j.querySelectorAll?.(Cj)?.[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};#F;#A=new Map;#K=new Map;#t;#M;#R;#x=0;#q=new Map;#B=new WeakMap;#_=new Map;#T=new WeakSet;#Y;#U;#H;#j;#X;#L;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.#D(),this.#L=()=>this.#D(),this.element.addEventListener?.("turbo:morph-element",this.#L);if(this.#e()){if(this.#Y=()=>this.#C(),this.element.addEventListener?.("turbo:morph-element",this.#Y),this.#C(),this.element.getAttribute?.("data-reactive-warn-unsaved")==="true")this.#Mj()}if(this.#Bj())this.#j=()=>this.#m(),this.element.addEventListener?.("input",this.#j),this.element.addEventListener?.("change",this.#j),this.element.addEventListener?.("turbo:morph-element",this.#j),this.#m();if(this.#Ej())this.#X=(j)=>{if(j?.type==="input"&&!this.#Fj(j))return;this.#d()},this.element.addEventListener?.("input",this.#X),this.element.addEventListener?.("turbo:morph-element",this.#X),this.#d()}#e(){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.#Kj(),this.#Yj(),this.#xj(),this.#Cj(),this.#Rj(),this.#L)this.element.removeEventListener?.("turbo:morph-element",this.#L)}#D(){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.#hj(j.params.loading);if(K&&this.element.contains(j.target))return;let _=j.currentTarget??j.target;if(!z&&!this.#Ij(Y,_))j.preventDefault();let W=this.#f(J,G);if(!W)return this.#w(_,Q,X,Z,$,Y,H);Promise.resolve().then(()=>k(W)).catch(()=>!1).then((A)=>{if(A)this.#w(_,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.#f(X,Z);if(!G)return this.#s(this.#o(Q));Promise.resolve().then(()=>k(G)).catch(()=>!1).then((K)=>{if(K)this.#s(this.#o(Q))})}trackDirty(){this.#C()}recompute(j){if(j&&this.#T.has(j))return;let Q=this.#Qj(),X=Q.map(([q])=>q),Z=this.element.getAttribute?.("data-reactive-scope")||null,$=(q)=>Z&&!q.includes("[")?`${Z}[${q}]`:q,J=this.#W(),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.#S(q,K(q)?.value??"");let z=this.element.getAttribute("data-reactive-compute-reducer-param"),Y=z?n(z):null;if(!Y){this.#k({},K);return}let H=this.#jj("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.#Xj(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.#S(q,U)}this.#k(W,K);for(let q of A){let U=new Event("input",{bubbles:!0});this.#T.add(U),q.dispatchEvent(U)}}listnavNext(j){this.#I(j,1)}listnavPrev(j){this.#I(j,-1)}listnavPick(j){let Q=this.#O(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.#O(j))Q.removeAttribute("data-reactive-highlighted")}#I(j,Q){let X=this.#O(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"})}#O(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.#W();return Array.from(this.element.querySelectorAll(X)).filter(($)=>!$.hidden&&Z($))}#jj(j){let Q=this.element.getAttribute(j);if(!Q)return[];try{let X=JSON.parse(Q);return Array.isArray(X)?X:[]}catch{return[]}}#Qj(){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[]}}#Xj(j,Q,X){let Z=j?.target;if(!Z?.name||typeof Z.closest!=="function")return null;let $=this.#Zj(Z.name,X);if(!Q.includes($))return null;return this.#Q(Z)?$:null}#Zj(j,Q){if(!Q)return j;let X=`${Q}[`;return j.startsWith(X)&&j.endsWith("]")?j.slice(X.length,-1):j}#S(j,Q){let X=String(Q);for(let Z of this.#$j(j)){if(Z.textContent===X)continue;Z.textContent=X}}#$j(j){let Q=this.element.querySelectorAll(`[data-reactive-text="${j}"]`);return Array.from(Q).filter((X)=>this.#Q(X))}#k(j,Q){let X=this.#Jj();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}}}}#Jj(){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{}}}#w(j,Q,X,Z,$,J,G){if(this.#V("reactive:before-dispatch",{action:Q,params:this.#l(X),element:this.element},{cancelable:!0}).defaultPrevented)return;let z=Number(Z)||0;if(z>0)return this.#zj(j,z,Q,X,J,G);let Y=Number($)||0;if(Y>0)return this.#qj(j,Y,Q,X,J,G);return this.#N(Q,X,J,j,G)}#N(j,Q,X,Z,$){let J=this.#Sj(X,Z),G=this.#kj(j,Z,$),K=this.#b()?this.#Gj(X,Z):null;return this.queue=(this.queue??Promise.resolve()).then(()=>this.#Lj(j,Q,J,G,K)),this.queue}#Gj(j,Q){if(!j?.hide)return null;let X=this.#a(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)}}#zj(j,Q,X,Z,$,J){this.#P(j);let G=()=>{this.#P(j),this.#N(X,Z,$,j,J)},K=setTimeout(G,Q);j?.addEventListener?.("blur",G,{once:!0}),this.#A.set(j,{timer:K,flush:G})}#P(j){let Q=this.#A.get(j);if(!Q)return;clearTimeout(Q.timer),j?.removeEventListener?.("blur",Q.flush),this.#A.delete(j)}#Kj(){for(let j of[...this.#A.keys()])this.#P(j)}#qj(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.#N(X,Z,$,j,J)}#Yj(){for(let j of this.#K.values())for(let Q of j.values())clearTimeout(Q);this.#K.clear()}#V(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.#N(j,Q)};this.#V("reactive:error",{action:j,params:X,...Z,retry:$})}#$(j){if(this.element?.isConnected===!1)return;this.element?.setAttribute?.("data-reactive-error",j)}#Uj(){this.element?.removeAttribute?.("data-reactive-error")}#Hj(){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))}#Wj(){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))}#b(){return this.element?.getAttribute?.("data-reactive-debug")==="true"}#v(){return typeof performance<"u"&&typeof performance.now==="function"?performance.now():Date.now()}#Aj(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}#_j(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#Lj(j,Q,X,Z,$){let{fields:J,files:G}=this.#u(),K=this.#l(Q),z={...J,...K},Y=this.#J,H=G.length>0,_=H?this.#Tj(Y,j,z,G):JSON.stringify({token:Y,act:j,params:z}),W=this.#b()?{action:j,paramNames:Object.keys(K),fieldNames:Object.keys(J),encoding:H?"multipart":"json",status:null,streams:[],tokenRefreshed:!1,started:this.#v()}:null;await this.#Wj();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.#pj()};if(!H)L["Content-Type"]="application/json";let M=this.#mj();if(M)L["X-Pgbus-Connection"]=M;A=await fetch(this.#fj(),{method:"POST",headers:L,body:_,credentials:"same-origin",signal:AbortSignal.timeout(this.#uj())})}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.#Hj(),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.#h(L);if(this.#J=M??this.#J,W)this.#y(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.#h(U);if(this.#J=V??this.#J,W)this.#y(W,U,V);if(window.Turbo.renderStreamMessage(U),$)queueMicrotask($);this.#Uj(),this.#V("reactive:applied",{action:j,params:z,html:U})}catch(A){console.error("[phlex-reactive] action error",A),this.#G(X),this.#V("reactive:error",{action:j,params:z,kind:"apply"})}finally{if(Z?.(),W)this.#_j({...W,ms:this.#v()-W.started})}}#y(j,Q,X){j.streams=this.#Aj(Q),j.tokenRefreshed=X!=null}get#J(){return this.#F??this.tokenValue}set#J(j){this.#F=j}#h(j){let Q=this.element.id;if(!Q)return j.match(/data-reactive-token-value="([^"]+)"/)?.[1];let{token:X,self:Z}=this.#Nj(Q),$=j.match(X);if($)return $[1];let J=j.match(Z);if(J)return J[1].match(/data-reactive-token-value="([^"]+)"/)?.[1];return}#Nj(j){let Q=this.#R;if(Q&&Q.id===j)return Q;let X=Wj(j);return this.#R={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}#W(){if(this.element.querySelectorAll('[data-controller~="reactive"]').length===0)return()=>!0;return(Q)=>this.#Q(Q)}#f(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.#W();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.#Vj(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")}#Vj(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}#p(){let j=this.element.getAttribute?.("data-reactive-dirty"),Q=Number(j);return Number.isFinite(Q)&&Q>0?Q:0}#Mj(){if(typeof window>"u"||typeof window.addEventListener!=="function")return;this.#U=(j)=>{if(this.#p()===0)return;return j.preventDefault(),j.returnValue="You have unsaved changes.",j.returnValue},this.#H=(j)=>{if(this.#p()===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}#Bj(){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}#m(){if(typeof this.element?.querySelectorAll!=="function")return;let j=this.#W(),Q=this.element.getAttribute?.("data-reactive-scope")||null,X=new Map,Z=($)=>{if(!X.has($))X.set($,this.#c($,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.#g($,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.#g($,z,j,Q)}this.#Oj(j,X,Q)}#g(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}#Oj(j,Q,X){let Z=this.#Pj();for(let[$,J]of Object.entries(Z)){if(!J||typeof J!=="object"||Array.isArray(J))continue;if(!Q.has($))Q.set($,this.#c($,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}}}#Pj(){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: { ... })"),{}}#c(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}#Ej(){return!!(this.element.getAttribute?.("data-reactive-filter-input")&&this.element.getAttribute?.("data-reactive-filter-option"))}#Fj(j){let Q=this.element.getAttribute("data-reactive-filter-input");return!!Q&&typeof j.target?.matches==="function"&&j.target.matches(Q)}#d(){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.#W(),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}}#Rj(){if(!this.#X)return;this.element.removeEventListener?.("input",this.#X),this.element.removeEventListener?.("turbo:morph-element",this.#X),this.#X=void 0}#Tj(j,Q,X,Z){let $=new FormData;$.append("token",j),$.append("act",Q);for(let[G,K]of Object.entries(X))this.#E($,`params[${G}]`,K);let J=this.#Dj(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 $}#E(j,Q,X){if(X==null)j.append(Q,"");else if(Array.isArray(X))X.forEach((Z,$)=>this.#E(j,`${Q}[${$}]`,Z));else if(typeof X==="object")for(let[Z,$]of Object.entries(X))this.#E(j,`${Q}[${Z}]`,$);else j.append(Q,String(X))}#Dj(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))}#l(j){if(!j)return{};try{return typeof j==="string"?JSON.parse(j):j}catch{return{}}}#o(j){return o(j)}#s(j){s(j,(Q)=>this.#i(Q))}#i(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))}#Ij(j,Q){if(j?.checked!=="keep")return!1;let X=Q?.type;return X==="checkbox"||X==="radio"}#Sj(j,Q){if(!j)return null;let X=this.#n(j,Q,!0);return X.length?X:null}#G(j){if(!j)return;if(!this.element.isConnected)return;for(let Q of j)Q()}#kj(j,Q,X){this.#bj(j,Q);let Z=X?this.#n(X,Q,!1):[],$=!1;return()=>{if($)return;$=!0,this.#vj(j,Q);for(let J of Z)J()}}#n(j,Q,X){let Z=[];for(let $ of this.#a(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.#wj(j,Q));if(X&&j.checked==="keep"&&Q&&"checked"in Q){let $=Q.checked;Z.push(()=>Q.checked=!$)}return Z}#a(j,Q){if(j.to==null)return Q?[Q]:[];return this.#i({to:j.to})}#wj(j,Q){let X=this.#_.get(Q);if(X)X.count++;else this.#_.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.#yj(Q,j)}#bj(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.#r(j))this.#z(X,j,1)}#vj(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.#r(j))this.#z(Z,j,-1)}#z(j,Q,X){if(!j||typeof j.getAttribute!=="function")return;let Z=this.#B.get(j)??new Map,$=(Z.get(Q)??0)+X;if($<=0)Z.delete(Q);else Z.set(Q,$);if(Z.size===0){this.#B.delete(j),j.removeAttribute("data-reactive-busy");return}this.#B.set(j,Z),j.setAttribute("data-reactive-busy",[...Z.keys()].join(" "))}#r(j){return[...this.element.querySelectorAll?.("[data-reactive-busy-on]")??[]].filter((X)=>X.getAttribute("data-reactive-busy-on")===j&&this.#Q(X))}#yj(j,Q){let X=this.#_.get(j);if(!X)return;if(--X.count>0)return;if(this.#_.delete(j),!j.isConnected)return;if(Q.disable)j.disabled=X.disabled;if(X.hadText&&j.innerHTML===X.swappedTo)j.innerHTML=X.html}#hj(j){if(!j||typeof j!=="object")return null;let{class:Q,...X}=j;return Q==null?j:{...X,add_class:Q}}#fj(){return this.#t??=document.querySelector('meta[name="phlex-reactive-action-path"]')?.content||"/reactive/actions"}#uj(){if(this.#M!=null)return this.#M;let j=document.querySelector('meta[name="phlex-reactive-timeout"]')?.content,Q=Number(j);return this.#M=Number.isFinite(Q)&&Q>0?Q:30000}#pj(){return document.querySelector('meta[name="csrf-token"]')?.content??""}#mj(){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=A6849C22D6EE177E64756E2164756E21
3
+ //# debugId=E7BCDAE133AC4CB864756E2164756E21
4
4
  //# sourceMappingURL=reactive_controller.min.js.map