phlex-reactive 0.9.2 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f48d899c85c31d10e7b864e0b4bd290258c3ccbcb4f97fa6052979bba3b68b1
4
- data.tar.gz: 39b93e42a39480822ec472e9211e5333ca448cec767d38f6c43569c8edcb09fe
3
+ metadata.gz: 86eeeabfa81b914dae1f3598e3c879892478c712afc5631b500b96cbd9b83757
4
+ data.tar.gz: d72882a3b92db0f2d542b5ef457a13f9e74fda538cb3ee5db7d2ac5916833515
5
5
  SHA512:
6
- metadata.gz: 22c03f299c2cf95e83cbbf9bd629a9d52308bc2631de44b76eab06fecee6b81313fdd2af6b4cf1de6574c533b0b3c059577ca258b0e2986bf254e5b56bbf1578
7
- data.tar.gz: 837312de90f9e82b785133de6b2fd69b7dbe2c44d34ce45bb68125469ad1d65daef08791a92c5b438a0e33dbb492dffd29638f638e07f47b8200eb4edd1abee2
6
+ metadata.gz: 884a4c34c7d26930bea2873bae0cf3a1ec0ff82d796179b01fe493187f8001d36283ea1e22daeaa347ab72ca8e74be31773cbba77bf0db41bb38781eb578c340
7
+ data.tar.gz: 4b75f6edec65f1d84954260305d384ad6674e21ee3fc5f51270e7055e7679a7d1bb209783d6678b2c506259bf6d1688c6a67c991cc013b22a6f66961bfec84f8
data/CHANGELOG.md CHANGED
@@ -8,6 +8,22 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
8
 
9
9
  ### Added
10
10
 
11
+ - **Value-conditional visibility — `reactive_show` (#161).** The `x-show` /
12
+ `data-show` / `wire:show` case — show/hide an element from a form field's
13
+ **current value** — no longer needs a hand-written `change`-listener Stimulus
14
+ controller. Spread `reactive_show(:mode, not: "off")` onto the element to
15
+ show/hide (also `equals:` and `in: [...]`; `equals: true` reads a checkbox's
16
+ checked state, a radio group reads the checked radio's value) and the generic
17
+ controller toggles the `hidden` attribute on every `input`/`change` —
18
+ client-only, zero round trip, no token. The predicate is a **declared literal
19
+ match**, never an expression (no eval surface); exactly one predicate is
20
+ enforced loudly at render; a missing field or malformed wire attr is
21
+ warn-and-skipped (client-side default-deny). Visibility seeds at connect and
22
+ re-syncs after a `turbo:morph-element`; a `reactive_compute` output write
23
+ dispatches a real `input` event, so derived values drive visibility too.
24
+ Ownership follows the nested-root rules (#15). Roots without a binding pay
25
+ one connect-time probe — no new listeners, byte-identical wire.
26
+
11
27
  - **Cross-root text mirrors + the `text` client op (#159).** A derived value can
12
28
  now be painted into a text node **outside** the computing component's reactive
13
29
  root — the read-only recap in another tab pane that previously forced a
data/README.md CHANGED
@@ -358,6 +358,7 @@ Use in controllers: `render turbo_stream: Counter.replace(counter)`.
358
358
  | `reactive_input(:param, **attrs)` / `reactive_select(:param, **attrs)` | Render a control already bound to an action param (no magic `name:`). |
359
359
  | `reactive_field(:param, **attrs)` | The attribute hash behind the above — spread onto any control. |
360
360
  | `reactive_text(:name, initial)` | Mirror a compute output (or a declared input) into a **text node** — a live preview heading, a character counter, `"Hello, {name}"` — via `textContent` (XSS-safe). The text sibling of `reactive_field`; carries no `name`, so it's never POSTed. See [Client-side computes](#client-side-computes-reactive_compute--reactive_text). |
361
+ | `reactive_show(:field, equals:/not:/in:)` | **Value-conditional visibility** (the `x-show`/`data-show` case): spread onto the element to show/hide — it toggles `hidden` from the named field's **current value**, client-only, zero round trip. One literal predicate: `equals:`, `not:`, or `in: [...]`; `equals: true` reads a checkbox's checked state. See [Value-conditional visibility](#value-conditional-visibility-reactive_show). |
361
362
  | `reactive_compute :name, inputs: { title: :string, qty: :number }, outputs:` | **Typed** inputs: a `:string` reaches the JS reducer raw, a `:number` is coerced through `Number`. The array form (`inputs: %i[a b]`) stays all-numeric. |
362
363
  | `reactive_compute :name, ..., mirror: { sum: "#summary-sum" }` | **Cross-root text mirrors**: paint a compute value into declared, id-allowlisted nodes **outside** the reactive root (a recap in another tab pane) via `textContent` — no bespoke listener. See [Cross-root mirrors](#cross-root-mirrors-mirror--painting-a-recap-outside-the-root). |
363
364
  | `reactive_root(track_dirty: true, warn_unsaved: true)` / `reactive_field(:param, dirty: true)` | **Dirty tracking** against the DOM's own `defaultValue`/`defaultChecked`/`defaultSelected` — no client state. Marks changed fields + the root `data-reactive-dirty`; `warn_unsaved:` arms a `beforeunload`/`turbo:before-visit` guard. Style with `[data-reactive-dirty]`. See [Dirty-field tracking](#dirty-field-tracking-dirty--track_dirty--warn_unsaved). |
@@ -855,6 +856,57 @@ free as an ordinary action-param name (`on(:switch, key: "pgbus")` still passes
855
856
  > trigger to its own element (the field saves on Enter; a Cancel button — or the
856
857
  > field's own blur — handles Escape), as above.
857
858
 
859
+ ### Value-conditional visibility (`reactive_show`)
860
+
861
+ `on_client` covers the *unconditional* client-only interactions; the last gap
862
+ was **show/hide from a form field's current value** — the Alpine `x-show` /
863
+ Datastar `data-show` / Livewire `wire:show` case ("reveal the details panel
864
+ *while* this select isn't `none`"). That used to force a hand-written
865
+ `change`-listener Stimulus controller back into an otherwise declarative form.
866
+ `reactive_show` closes it: spread it onto the element to show/hide, name the
867
+ controlling field, declare **one literal predicate** — the generic controller
868
+ toggles the `hidden` attribute from the field's current value on every
869
+ `input`/`change`. Client-only, **no token, no POST, ever**:
870
+
871
+ ```ruby
872
+ def view_template
873
+ div(**reactive_root) do
874
+ select(name: "mode") { shipping_options }
875
+ div(**reactive_show(:mode, not: "off", hidden: @order.mode == "off")) { shipping_details }
876
+
877
+ input(type: "checkbox", name: "gift")
878
+ div(**reactive_show(:gift, equals: true, hidden: true)) { gift_message_field }
879
+
880
+ select(name: "size") { size_options }
881
+ div(**reactive_show(:size, in: %w[l xl], hidden: true)) { surcharge_note }
882
+ end
883
+ end
884
+ ```
885
+
886
+ - **One predicate per binding** — `equals:`, `not:`, or `in:` (a list); zero or
887
+ two raise at render (a dead binding must not silently no-op). Values are
888
+ **stringified literals** matched against the field's value — never an
889
+ expression, so there is no eval surface.
890
+ - **Field reads follow the collection rules**: a checkbox compares its
891
+ *checked* state as `"true"`/`"false"` (so `equals: true` is the checkbox
892
+ form — its `.value` is the constant `"on"`, and it wins over the hidden
893
+ input Rails pairs with it); a radio group reads the **checked** radio's
894
+ value (`""` when none is); everything else reads `.value`. Ownership is the
895
+ usual rule — a nested reactive component's fields and bindings belong to the
896
+ nested component.
897
+ - **Initial state**: the client seeds visibility at connect and reconciles
898
+ after a morph, but render the initial `hidden:` yourself (from the same
899
+ server state that renders the field, as above) so the first paint doesn't
900
+ flash.
901
+ - **Extra attrs ride through the helper** (`reactive_show(:mode, not: "off",
902
+ class: "panel", data: { testid: "details" })`) and deep-merge — a bare
903
+ `data:` spread *beside* it would clobber the binding, same as `on(...)`.
904
+ - **Composes with computes**: a `reactive_compute` output write dispatches a
905
+ real `input` event, so a *derived* value can drive visibility too.
906
+ - Presentational only, strictly weaker than the js ops: it reads an owned
907
+ field and toggles `hidden` on an owned element — no `innerHTML`, no
908
+ attribute freedom, no cross-root writes.
909
+
858
910
  ### Client-side computes (`reactive_compute` + `reactive_text`)
859
911
 
860
912
  Some math should feel instant with **no round trip** — a NEW, unsaved record's
@@ -443,6 +443,36 @@ function guardMirrorSelector(selector) {
443
443
  return false
444
444
  }
445
445
 
446
+ // Evaluate a show binding's declared literal predicate (issue #161) against
447
+ // the controlling field's current value. Exactly one of the three predicate
448
+ // attrs decides: equals (value === literal), not (value !== literal), in
449
+ // (value ∈ a JSON string list). The vocabulary is fixed and literal-only —
450
+ // never an expression, so there is no eval surface (the reactive_show helper
451
+ // enforces the same shape loudly at render; this is the client half of the
452
+ // two-sided posture). Returns true/false for a decidable binding, or null for
453
+ // a malformed/missing predicate — the caller SKIPS a null so a hand-built or
454
+ // stale binding never flips visibility it doesn't understand (default-deny,
455
+ // like the op whitelist).
456
+ function showBindingMatches(el, value) {
457
+ const equals = el.getAttribute("data-reactive-show-equals")
458
+ if (equals !== null) return value === equals
459
+ const not = el.getAttribute("data-reactive-show-not")
460
+ if (not !== null) return value !== not
461
+ const inRaw = el.getAttribute("data-reactive-show-in")
462
+ if (inRaw !== null) {
463
+ try {
464
+ const list = JSON.parse(inRaw)
465
+ if (Array.isArray(list)) return list.includes(value)
466
+ } catch {
467
+ // fall through to the warn below — malformed JSON and a non-array both skip
468
+ }
469
+ console.warn(`[phlex-reactive] malformed reactive_show in: list ${JSON.stringify(inRaw)} — skipped`)
470
+ return null
471
+ }
472
+ console.warn("[phlex-reactive] a reactive_show binding declares no predicate — skipped")
473
+ return null
474
+ }
475
+
446
476
  // The first focusable descendant of `el`, in document order — the natural
447
477
  // keyboard target inside an opened menu/dialog. Covers the standard focusable
448
478
  // set; :not([tabindex="-1"]) drops explicitly-removed nodes. Returns null when
@@ -535,6 +565,9 @@ export default class extends Controller {
535
565
  #boundScanDirty
536
566
  #boundBeforeUnload
537
567
  #boundBeforeVisit
568
+ // Show bindings (issue #161): the ONE delegated sync handler shared by the
569
+ // root's input/change/turbo:morph-element listeners, held for teardown.
570
+ #boundSyncShow
538
571
 
539
572
  // Mark that a reactive controller actually connected, so the registration
540
573
  // guard above knows the controller was registered (issue #26 part 2).
@@ -581,6 +614,23 @@ export default class extends Controller {
581
614
  this.#armUnsavedGuard()
582
615
  }
583
616
  }
617
+
618
+ // Show bindings (issue #161) — ONLY when this root owns one, so a component
619
+ // without any pays a single probe (the dirty-tracking gate precedent). ONE
620
+ // delegated listener pair on the root (input + change bubble from every
621
+ // owned field — no per-field wiring, and a reactive_compute output write
622
+ // dispatches a real input event, so computed values drive visibility too).
623
+ // The connect sync seeds the initial state — a plain replace re-connects —
624
+ // and turbo:morph-element re-syncs after an in-place morph (which keeps the
625
+ // element connected, fires no Stimulus lifecycle, and may preserve a
626
+ // user-edited field value the server's hidden attrs don't reflect).
627
+ if (this.#showSyncEnabled()) {
628
+ this.#boundSyncShow = () => this.#syncShow()
629
+ this.element.addEventListener?.("input", this.#boundSyncShow)
630
+ this.element.addEventListener?.("change", this.#boundSyncShow)
631
+ this.element.addEventListener?.("turbo:morph-element", this.#boundSyncShow)
632
+ this.#syncShow()
633
+ }
584
634
  }
585
635
 
586
636
  // Whether this root opts into dirty tracking (issue #103): track_dirty: puts the
@@ -605,6 +655,7 @@ export default class extends Controller {
605
655
  this.#clearAllDebounces()
606
656
  this.#clearAllThrottles()
607
657
  this.#teardownDirtyTracking()
658
+ this.#teardownShowSync()
608
659
  }
609
660
 
610
661
  // Serialize requests per component. Each round trip rewrites the signed
@@ -1773,6 +1824,76 @@ export default class extends Controller {
1773
1824
  this.#boundBeforeVisit = undefined
1774
1825
  }
1775
1826
 
1827
+ // Whether this root owns a show binding (issue #161) — the connect() gate, so
1828
+ // a component without one pays only this probe (the #dirtyTrackingEnabled
1829
+ // precedent). A NESTED root's bindings don't count: its own controller
1830
+ // instance syncs them (issue #15 ownership).
1831
+ #showSyncEnabled() {
1832
+ const nodes = this.element.querySelectorAll?.("[data-reactive-show-field]") ?? []
1833
+ for (const el of nodes) if (this.#ownsField(el)) return true
1834
+ return false
1835
+ }
1836
+
1837
+ // Re-evaluate every OWNED show binding in one pass (issue #161): read the
1838
+ // controlling field's current value, evaluate the declared literal predicate,
1839
+ // toggle `hidden`. A full pass (not per-target) for the same reason as
1840
+ // #scanDirty — a radio group's deselected radio fires no event — and because
1841
+ // several bindings can hang off one field (the value read is memoized per
1842
+ // pass). A binding whose field can't be resolved, or whose predicate is
1843
+ // malformed, leaves visibility ALONE — a bad binding must never break or
1844
+ // blank the page (client-side default-deny).
1845
+ #syncShow() {
1846
+ if (typeof this.element?.querySelectorAll !== "function") return
1847
+
1848
+ const owns = this.#ownershipFilter()
1849
+ const values = new Map()
1850
+ for (const el of this.element.querySelectorAll("[data-reactive-show-field]")) {
1851
+ if (!owns(el)) continue // a nested root's binding is its own controller's job
1852
+ const name = el.getAttribute("data-reactive-show-field")
1853
+ if (!name) continue
1854
+ if (!values.has(name)) values.set(name, this.#showFieldValue(name, owns))
1855
+ const value = values.get(name)
1856
+ if (value === null) continue // no owned field with that name — leave it be
1857
+ const match = showBindingMatches(el, value)
1858
+ if (match === null) continue // malformed predicate — warned + skipped
1859
+ el.hidden = !match
1860
+ }
1861
+ }
1862
+
1863
+ // The current value of the OWNED field controlling a show binding, as the
1864
+ // string the literal predicate compares against. Mirrors #collectFields'
1865
+ // per-kind reads: a checkbox reports its checked state ("true"/"false" — its
1866
+ // .value is the constant "on", and the checkbox wins over the hidden input
1867
+ // Rails pairs with it); a radio group reports the CHECKED radio's value (""
1868
+ // when none is); anything else reports .value first-wins. Returns null when
1869
+ // no owned field carries the name — the caller then leaves visibility alone.
1870
+ #showFieldValue(name, owns) {
1871
+ let sawRadio = false
1872
+ let first = null
1873
+ for (const el of this.element.querySelectorAll(`[name="${name}"]`)) {
1874
+ if (!owns(el)) continue
1875
+ if (el.type === "checkbox") return el.checked ? "true" : "false"
1876
+ if (el.type === "radio") {
1877
+ if (el.checked) return el.value ?? ""
1878
+ sawRadio = true
1879
+ continue
1880
+ }
1881
+ first ??= el
1882
+ }
1883
+ if (first) return first.value ?? ""
1884
+ return sawRadio ? "" : null
1885
+ }
1886
+
1887
+ // Remove the show-sync listeners on disconnect, so a stray event after a
1888
+ // Turbo morph/navigation never re-evaluates against a detached root.
1889
+ #teardownShowSync() {
1890
+ if (!this.#boundSyncShow) return
1891
+ this.element.removeEventListener?.("input", this.#boundSyncShow)
1892
+ this.element.removeEventListener?.("change", this.#boundSyncShow)
1893
+ this.element.removeEventListener?.("turbo:morph-element", this.#boundSyncShow)
1894
+ this.#boundSyncShow = undefined
1895
+ }
1896
+
1776
1897
  // Build the multipart body (issue #34). `token`/`act` are flat fields the
1777
1898
  // endpoint reads from params[:token]/params[:act]; scalar params nest under
1778
1899
  // params[<key>] (Rails parses the bracket into params[:params]); each file is
@@ -1,4 +1,4 @@
1
- import{Controller as R}from"@hotwired/stimulus";import{confirmResolver as T}from"phlex/reactive/confirm";import{computeReducer as k}from"phlex/reactive/compute";function w(){let j=window.Turbo?.StreamActions;if(!j||j["reactive:visit"])return;j["reactive:visit"]=function(){let z=this.getAttribute("data-url");if(z)window.Turbo.visit(z,{action:"advance"})}}function b(){let j=window.Turbo?.StreamActions;if(!j||j["reactive:token"])return;j["reactive:token"]=function(){let z=this.getAttribute("data-reactive-token-value"),Q=this.getAttribute("target");if(!z||!Q)return;let W=document.getElementById(Q);if(W)W.setAttribute("data-reactive-token-value",z)}}function h(){let j=window.Turbo?.StreamActions;if(!j||j["reactive:js"])return;j["reactive:js"]=function(){let z=C(this.getAttribute("data-reactive-ops"));if(!z.length)return;let Q=this.getAttribute("target"),W=Q?document.getElementById(Q):null;if(Q&&!W)return;B(z,(X)=>i(X,W))}}var O=!1;function y(){if(O)return;if(typeof document>"u"||!document.addEventListener)return;O=!0,document.addEventListener("turbo:before-stream-render",v)}function v(j){let z=j.detail,Q=z?.render;if(typeof Q!=="function"||Q.__reactiveDismissWrapped){if(typeof requestAnimationFrame==="function")requestAnimationFrame(L);else setTimeout(L,0);return}let W=async(X)=>{await Q(X),L()};W.__reactiveDismissWrapped=!0,z.render=W}function L(){let j=document.querySelectorAll("[data-reactive-dismiss-after]");for(let z of j){if(z.hasAttribute("data-reactive-dismiss-scheduled"))continue;let Q=Number(z.getAttribute("data-reactive-dismiss-after"));if(!Number.isFinite(Q)||Q<=0)continue;z.setAttribute("data-reactive-dismiss-scheduled",""),setTimeout(()=>z.remove(),Q)}}function zj(){O=!1}var x=!1;function f(){if(x)return;if(typeof window>"u"||typeof document>"u")return;if(typeof window.addEventListener!=="function")return;x=!0;let j=()=>{let z=document.documentElement;if(typeof z?.toggleAttribute!=="function")return;z.toggleAttribute("data-reactive-offline",globalThis.navigator?.onLine===!1)};j(),window.addEventListener("online",j),window.addEventListener("offline",j)}function Qj(){x=!1}var P="phlex-reactive:latency",I=!1;function u(j){if(typeof sessionStorage>"u")return;sessionStorage.setItem(P,String(j))}function p(){if(typeof sessionStorage>"u")return;sessionStorage.removeItem(P),I=!1}function m(){if(typeof window>"u"||typeof document>"u")return;if(document.querySelector?.('meta[name="phlex-reactive-env"]')?.content!=="development")return;window.PhlexReactive={enableLatencySim:u,disableLatencySim:p}}function Wj(){I=!1}function S(){w(),b(),h(),y(),f(),m()}function c(j){return j.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}if(typeof window<"u")if(window.Turbo)S();else document.addEventListener("turbo:load",S,{once:!0});var N=!1;function g(){if(N)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 Xj(){N=!1}function Zj(){N=!0}if(typeof window<"u"&&typeof document<"u"){let j=()=>setTimeout(g,0);if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",j,{once:!0});else j()}var d=new Set(["href","src","srcdoc","action","formaction","xlink:href","style"]);function l(j){let z=String(j).toLowerCase();return z.startsWith("on")||d.has(z)}function s(j,z,Q){let[W,X,Z]=z;j.classList.add(W,X),Q(),requestAnimationFrame(()=>{j.classList.remove(X),j.classList.add(Z)});let $=!1,G=()=>{if($)return;$=!0,j.classList.remove(W,Z)};j.addEventListener("animationend",G,{once:!0}),setTimeout(G,350)}var E=Object.freeze({show:(j,z)=>A(j,!1,z),hide:(j,z)=>A(j,!0,z),toggle:(j,z)=>A(j,!j.hidden,z),add_class:(j,z)=>j.classList.add(...z.classes??[]),remove_class:(j,z)=>j.classList.remove(...z.classes??[]),toggle_class:(j,z)=>(z.classes??[]).forEach((Q)=>j.classList.toggle(Q)),set_attr:(j,z)=>{if(D(z.name))j.setAttribute(z.name,z.value??"")},remove_attr:(j,z)=>{if(D(z.name))j.removeAttribute(z.name)},toggle_attr:(j,z)=>{if(!D(z.name))return;if(j.hasAttribute(z.name))j.removeAttribute(z.name);else j.setAttribute(z.name,"")},focus:(j)=>j.focus?.(),focus_first:(j)=>r(j)?.focus?.(),text:(j,z)=>{let Q=String(z.value??"");if(j.textContent!==Q)j.textContent=Q},dispatch:(j,z)=>{j.dispatchEvent(new CustomEvent(z.name,{bubbles:!0,composed:!0,detail:z.detail??{}}))}});function A(j,z,Q){if(Q?.transition)s(j,Q.transition,()=>j.hidden=z);else j.hidden=z}function D(j){if(!l(j))return!0;return console.warn(`[phlex-reactive] refused client attr op on ${JSON.stringify(j)} — skipped`),!1}var o=/^#[A-Za-z_][\w-]*$/;function n(j){if(typeof j==="string"&&o.test(j))return!0;return console.warn(`[phlex-reactive] refused cross-root mirror target ${JSON.stringify(j)} — skipped`),!1}var a='a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';function r(j){return j.querySelectorAll?.(a)?.[0]??null}function C(j){if(Array.isArray(j))return j;if(typeof j!=="string")return[];try{let z=JSON.parse(j);return Array.isArray(z)?z:[]}catch{return[]}}function B(j,z){for(let Q of j){if(!Array.isArray(Q))continue;let[W,X={}]=Q;if(!Object.hasOwn(E,W)){console.warn(`[phlex-reactive] unknown client op ${JSON.stringify(W)} — skipped`);continue}for(let Z of z(X))E[W](Z,X)}}function i(j,z){let Q=j.to;if(z){if(Q==="@root")return[z];if(typeof Q!=="string"||Q==="")return[];if(j.global)return[...document.querySelectorAll(Q)];return[...z.querySelectorAll(Q)]}if(typeof Q!=="string"||Q===""||Q==="@root")return[];return[...document.querySelectorAll(Q)]}class $j extends R{static values={token:String};#P;#Y=new Map;#$=new Map;#y;#_;#F;#M=0;#G=new Map;#I=new WeakMap;#V=new Map;#J;#q;#K;connect(){if(N=!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.#v()){if(this.#J=()=>this.#D(),this.element.addEventListener?.("turbo:morph-element",this.#J),this.#D(),this.element.getAttribute?.("data-reactive-warn-unsaved")==="true")this.#Wj()}}#v(){if((this.element.getAttribute?.("data-action")??"").includes("reactive#trackDirty"))return!0;let j=this.element.querySelectorAll?.('[data-action*="reactive#trackDirty"]')??[];for(let z of j)if(this.#j(z))return!0;return!1}disconnect(){this.#d(),this.#s(),this.#Xj()}dispatch(j){let{action:z,params:Q,debounce:W,throttle:X,confirm:Z,outside:$,window:G,optimistic:K,loading:Y}=j.params;if(!z)return;if($&&this.element.contains(j.target))return;let V=j.currentTarget??j.target;if(!G&&!this.#qj(K,V))j.preventDefault();if(!Z)return this.#B(V,z,Q,W,X,K,Y);Promise.resolve().then(()=>T(Z)).catch(()=>!1).then((J)=>{if(J)this.#B(V,z,Q,W,X,K,Y)})}runOps(j){let{ops:z,outside:Q,window:W}=j.params;if(Q&&this.element.contains(j.target))return;if(!W)j.preventDefault();this.#Jj(this.#Gj(z))}trackDirty(){this.#D()}recompute(j){let z=this.#u(),Q=z.map(([J])=>J),W=this.#A(),X=new Map,Z=(J)=>{if(X.has(J))return X.get(J);let q=null;for(let U of this.element.querySelectorAll(`[name="${J}"]`))if(W(U)){q=U;break}return X.set(J,q),q};for(let J of Q)this.#E(J,Z(J)?.value??"");let $=this.element.getAttribute("data-reactive-compute-reducer-param"),G=$?k($):null;if(!G){this.#C({},Z);return}let K=this.#f("data-reactive-compute-outputs-param"),Y={};for(let[J,q]of z){let U=Z(J);if(q==="string")Y[J]=U?.value??"";else{let _=Number(U?.value);Y[J]=Number.isFinite(_)?_:0}}let V=G(Y,{changed:this.#p(j,Q)})||{};for(let J of K){if(!(J in V))continue;let q=Z(J);if(q){if(String(V[J])===q.value)continue;q.value=V[J],q.dispatchEvent(new Event("input",{bubbles:!0}))}else this.#E(J,V[J])}this.#C(V,Z)}listnavNext(j){this.#S(j,1)}listnavPrev(j){this.#S(j,-1)}listnavPick(j){let z=this.#N(j),Q=z.findIndex((W)=>W.hasAttribute("data-reactive-highlighted"));if(Q<0)return;j.preventDefault(),z[Q].click()}listnavClose(j){for(let z of this.#N(j))z.removeAttribute("data-reactive-highlighted")}#S(j,z){let Q=this.#N(j);if(!Q.length)return;j.preventDefault();let W=Q.findIndex(($)=>$.hasAttribute("data-reactive-highlighted")),X=W<0?z>0?0:Q.length-1:(W+z+Q.length)%Q.length;for(let $ of Q)$.removeAttribute("data-reactive-highlighted");let Z=Q[X];Z.setAttribute("data-reactive-highlighted","true"),Z.scrollIntoView?.({block:"nearest"})}#N(j){let Q=(j?.currentTarget??j?.target??this.element).getAttribute?.("data-reactive-listnav-option-param")??this.element.getAttribute("data-reactive-listnav-option-param");if(!Q)return[];let W=this.#A();return Array.from(this.element.querySelectorAll(Q)).filter(W)}#f(j){let z=this.element.getAttribute(j);if(!z)return[];try{let Q=JSON.parse(z);return Array.isArray(Q)?Q:[]}catch{return[]}}#u(){let j=this.element.getAttribute("data-reactive-compute-inputs-param");if(!j)return[];try{let z=JSON.parse(j);if(Array.isArray(z))return z.map((Q)=>[Q,"number"]);if(z&&typeof z==="object")return Object.entries(z);return[]}catch{return[]}}#p(j,z){let Q=j?.target;if(!Q?.name||typeof Q.closest!=="function")return null;if(!z.includes(Q.name))return null;return this.#j(Q)?Q.name:null}#E(j,z){let Q=String(z);for(let W of this.#m(j)){if(W.textContent===Q)continue;W.textContent=Q}}#m(j){let z=this.element.querySelectorAll(`[data-reactive-text="${j}"]`);return Array.from(z).filter((Q)=>this.#j(Q))}#C(j,z){let Q=this.#c();for(let[W,X]of Object.entries(Q)){let Z=W in j?j[W]:z(W)?.value;if(Z===void 0||Z===null)continue;let $=String(Z);for(let G of Array.isArray(X)?X:[X]){if(!n(G))continue;for(let K of document.querySelectorAll(G)){if(K.textContent===$)continue;K.textContent=$}}}}#c(){let j=this.element.getAttribute("data-reactive-compute-mirror-param");if(!j)return{};try{let z=JSON.parse(j);return z&&typeof z==="object"&&!Array.isArray(z)?z:{}}catch{return{}}}#B(j,z,Q,W,X,Z,$){if(this.#U("reactive:before-dispatch",{action:z,params:this.#b(Q),element:this.element},{cancelable:!0}).defaultPrevented)return;let K=Number(W)||0;if(K>0)return this.#g(j,K,z,Q,Z,$);let Y=Number(X)||0;if(Y>0)return this.#l(j,Y,z,Q,Z,$);return this.#H(z,Q,Z,j,$)}#H(j,z,Q,W,X){let Z=this.#Kj(Q,W),$=this.#Vj(j,W,X);return this.queue=(this.queue??Promise.resolve()).then(()=>this.#e(j,z,Z,$)),this.queue}#g(j,z,Q,W,X,Z){this.#L(j);let $=()=>{this.#L(j),this.#H(Q,W,X,j,Z)},G=setTimeout($,z);j?.addEventListener?.("blur",$,{once:!0}),this.#Y.set(j,{timer:G,flush:$})}#L(j){let z=this.#Y.get(j);if(!z)return;clearTimeout(z.timer),j?.removeEventListener?.("blur",z.flush),this.#Y.delete(j)}#d(){for(let j of[...this.#Y.keys()])this.#L(j)}#l(j,z,Q,W,X,Z){let $=this.#$.get(j)??new Map;if($.has(Q))return;let G=setTimeout(()=>{if($.delete(Q),$.size===0)this.#$.delete(j)},z);return $.set(Q,G),this.#$.set(j,$),this.#H(Q,W,X,j,Z)}#s(){for(let j of this.#$.values())for(let z of j.values())clearTimeout(z);this.#$.clear()}#U(j,z,{cancelable:Q=!1}={}){let W=new CustomEvent(j,{bubbles:!0,composed:!0,cancelable:Q,detail:z});return(this.element.isConnected?this.element:document).dispatchEvent(W),W}#z(j,z,Q,W){let X=()=>{if(!this.element.isConnected){console.warn("[phlex-reactive] retry() ignored — the reactive root left the DOM");return}return this.#H(j,z)};this.#U("reactive:error",{action:j,params:Q,...W,retry:X})}#Q(j){if(this.element?.isConnected===!1)return;this.element?.setAttribute?.("data-reactive-error",j)}#o(){this.element?.removeAttribute?.("data-reactive-error")}#n(){let j=document.querySelector("[data-reactive-error-flash]");if(!j?.content)return;let z=j.getAttribute("data-reactive-error-flash")||"flash",Q=document.getElementById(z);if(!Q)return;Q.appendChild(j.content.cloneNode(!0))}#a(){if(typeof sessionStorage>"u")return Promise.resolve();let j=Number(sessionStorage.getItem(P));if(!Number.isFinite(j)||j<=0)return Promise.resolve();if(!I)I=!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((z)=>setTimeout(z,j))}#r(){return this.element?.getAttribute?.("data-reactive-debug")==="true"}#R(){return typeof performance<"u"&&typeof performance.now==="function"?performance.now():Date.now()}#i(j){if(!j)return[];let z=[],Q=/<turbo-stream\b([^>]*)>/g,W;while((W=Q.exec(j))!==null){let X=W[1],Z=X.match(/\baction="([^"]*)"/)?.[1]??"?",$=X.match(/\btarget="([^"]*)"/)?.[1];z.push($?`${Z} → #${$}`:Z)}return z}#t(j){let{action:z,status:Q,ms:W}=j,Z=`reactive ${this.element?.id?`#${this.element.id} `:""}${z} → ${Q??"—"} (${Math.round(W)}ms)`;if(console.groupCollapsed(Z),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#e(j,z,Q,W){let{fields:X,files:Z}=this.#zj(),$=this.#b(z),G={...X,...$},K=this.#W,Y=Z.length>0,V=Y?this.#Zj(K,j,G,Z):JSON.stringify({token:K,act:j,params:G}),J=this.#r()?{action:j,paramNames:Object.keys($),fieldNames:Object.keys(X),encoding:Y?"multipart":"json",status:null,streams:[],tokenRefreshed:!1,started:this.#R()}:null;await this.#a();try{if(navigator.onLine===!1){this.#X(Q),this.#Q("offline"),this.#z(j,z,G,{kind:"offline"});return}let q;try{let H={Accept:"text/vnd.turbo-stream.html","X-CSRF-Token":this.#Aj()};if(!Y)H["Content-Type"]="application/json";let M=this.#Dj();if(M)H["X-Pgbus-Connection"]=M;q=await fetch(this.#Nj(),{method:"POST",headers:H,body:V,credentials:"same-origin",signal:AbortSignal.timeout(this.#Lj())})}catch(H){if(console.error("[phlex-reactive] action error",H),this.#X(Q),H?.name==="TimeoutError"||H?.name==="AbortError"){this.#Q("timeout"),this.#z(j,z,G,{kind:"timeout"});return}this.#n(),this.#Q("network"),this.#z(j,z,G,{kind:"network"});return}if(J)J.status=q.status;if(q.redirected){console.error("[phlex-reactive] action was redirected (auth/CSRF?) — no update applied"),this.#X(Q),this.#Q("redirected"),this.#z(j,z,G,{kind:"redirected",status:q.status});return}if(!q.ok){let H=await q.text();if(console.error(`[phlex-reactive] action failed: HTTP ${q.status}`,H),this.#X(Q),(q.headers.get("Content-Type")||"").includes("turbo-stream")){let M=this.#k(H);if(this.#W=M??this.#W,J)this.#T(J,H,M);window.Turbo.renderStreamMessage(H)}this.#Q("http"),this.#z(j,z,G,{kind:"http",status:q.status,body:H});return}let U=q.headers.get("Content-Type")||"";if(!U.includes("turbo-stream")){console.error(`[phlex-reactive] expected a turbo-stream, got "${U}" — no update applied`),this.#X(Q),this.#Q("content-type"),this.#z(j,z,G,{kind:"content-type",status:q.status});return}let _=await q.text(),F=this.#k(_);if(this.#W=F??this.#W,J)this.#T(J,_,F);window.Turbo.renderStreamMessage(_),this.#o(),this.#U("reactive:applied",{action:j,params:G,html:_})}catch(q){console.error("[phlex-reactive] action error",q),this.#X(Q),this.#U("reactive:error",{action:j,params:G,kind:"apply"})}finally{if(W?.(),J)this.#t({...J,ms:this.#R()-J.started})}}#T(j,z,Q){j.streams=this.#i(z),j.tokenRefreshed=Q!=null}get#W(){return this.#P??this.tokenValue}set#W(j){this.#P=j}#k(j){let z=this.element.id;if(!z)return j.match(/data-reactive-token-value="([^"]+)"/)?.[1];let{token:Q,self:W}=this.#jj(z),X=j.match(Q);if(X)return X[1];let Z=j.match(W);if(Z)return Z[1].match(/data-reactive-token-value="([^"]+)"/)?.[1];return}#jj(j){let z=this.#F;if(z&&z.id===j)return z;let Q=c(j);return this.#F={id:j,token:new RegExp(`<turbo-stream\\b[^>]*\\baction="reactive:token"[^>]*\\btarget="${Q}"[^>]*\\bdata-reactive-token-value="([^"]+)"`),self:new RegExp(`<turbo-stream\\b[^>]*\\baction="(?:replace|update)"[^>]*\\btarget="${Q}"[^>]*>([\\s\\S]*?)</turbo-stream>`)}}#j(j){return j.closest('[data-controller~="reactive"]')===this.element}#A(){if(this.element.querySelectorAll('[data-controller~="reactive"]').length===0)return()=>!0;return(z)=>this.#j(z)}#zj(){let j={},z=[],Q=this.#A();return this.element.querySelectorAll("input[name], select[name], textarea[name]").forEach((W)=>{if(!Q(W))return;if(W.type==="file")for(let X of W.files??[])z.push({name:W.name,file:X,multiple:W.multiple});else if(W.type==="checkbox")j[W.name]=W.checked;else if(W.type==="radio"){if(W.checked)j[W.name]=W.value}else j[W.name]=W.value}),this.element.querySelectorAll("[name]:is(lexxy-editor, trix-editor, [contenteditable=''], [contenteditable=true], [contenteditable=plaintext-only])").forEach((W)=>{if(!Q(W))return;let X=W.getAttribute("name");if(!X)return;let Z=j[X];if(Z==null||Z==="")j[X]=W.value??W.textContent??W.innerHTML??""}),{fields:j,files:z}}#D(){if(typeof this.element?.querySelectorAll!=="function")return;let j=0;if(this.element.querySelectorAll("input[name], select[name], textarea[name]").forEach((z)=>{if(!this.#j(z))return;if(z.type==="file")return;if(this.#Qj(z))z.setAttribute("data-reactive-dirty","true"),j++;else z.removeAttribute("data-reactive-dirty")}),j>0)this.element.setAttribute("data-reactive-dirty",String(j));else this.element.removeAttribute("data-reactive-dirty")}#Qj(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((z)=>z.selected!==z.defaultSelected);return j.value!==j.defaultValue}#w(){let j=this.element.getAttribute?.("data-reactive-dirty"),z=Number(j);return Number.isFinite(z)&&z>0?z:0}#Wj(){if(typeof window>"u"||typeof window.addEventListener!=="function")return;this.#q=(j)=>{if(this.#w()===0)return;return j.preventDefault(),j.returnValue="You have unsaved changes.",j.returnValue},this.#K=(j)=>{if(this.#w()===0)return;if(!(typeof window.confirm==="function"?window.confirm("You have unsaved changes. Leave anyway?"):!0))j.preventDefault?.()},window.addEventListener("beforeunload",this.#q),window.addEventListener("turbo:before-visit",this.#K)}#Xj(){if(this.#J)this.element.removeEventListener?.("turbo:morph-element",this.#J),this.#J=void 0;if(typeof window<"u"&&typeof window.removeEventListener==="function"){if(this.#q)window.removeEventListener("beforeunload",this.#q);if(this.#K)window.removeEventListener("turbo:before-visit",this.#K)}this.#q=void 0,this.#K=void 0}#Zj(j,z,Q,W){let X=new FormData;X.append("token",j),X.append("act",z);for(let[$,G]of Object.entries(Q))this.#O(X,`params[${$}]`,G);let Z=this.#$j(W);for(let{name:$,file:G,multiple:K}of W){let V=K||Z.has($)?`params[${$}][]`:`params[${$}]`;X.append(V,G,G.name)}return X}#O(j,z,Q){if(Q==null)j.append(z,"");else if(Array.isArray(Q))Q.forEach((W,X)=>this.#O(j,`${z}[${X}]`,W));else if(typeof Q==="object")for(let[W,X]of Object.entries(Q))this.#O(j,`${z}[${W}]`,X);else j.append(z,String(Q))}#$j(j){let z=new Map;for(let{name:Q}of j)z.set(Q,(z.get(Q)??0)+1);return new Set([...z].filter(([,Q])=>Q>1).map(([Q])=>Q))}#b(j){if(!j)return{};try{return typeof j==="string"?JSON.parse(j):j}catch{return{}}}#Gj(j){return C(j)}#Jj(j){B(j,(z)=>this.#x(z))}#x(j){let z=j.to;if(z==="@root")return[this.element];if(typeof z!=="string"||z==="")return[];if(j.global)return[...document.querySelectorAll(z)];return[...this.element.querySelectorAll(z)].filter((Q)=>this.#j(Q))}#qj(j,z){if(j?.checked!=="keep")return!1;let Q=z?.type;return Q==="checkbox"||Q==="radio"}#Kj(j,z){if(!j)return null;let Q=this.#Yj(j,z),W=[];for(let X of Q){if(j.add_class){let Z=j.add_class.filter(($)=>!X.classList.contains($));if(X.classList.add(...Z),Z.length)W.push(()=>X.classList.remove(...Z))}if(j.remove_class){let Z=j.remove_class.filter(($)=>X.classList.contains($));if(X.classList.remove(...Z),Z.length)W.push(()=>X.classList.add(...Z))}if(j.toggle_class)j.toggle_class.forEach((Z)=>X.classList.toggle(Z)),W.push(()=>j.toggle_class.forEach((Z)=>X.classList.toggle(Z)));if(j.hide)X.hidden=!0,W.push(()=>X.hidden=!1)}if(j.checked==="keep"&&z&&"checked"in z){let X=z.checked;W.push(()=>z.checked=!X)}return W.length?W:null}#X(j){if(!j)return;if(!this.element.isConnected)return;for(let z of j)z()}#Yj(j,z){if(j.to==null)return z?[z]:[];return this.#x({to:j.to})}#Vj(j,z,Q){this.#Hj(j,z);let W=this.#_j(j,z,Q),X=!1;return()=>{if(X)return;X=!0,this.#Uj(j,z),W()}}#Hj(j,z){if(this.#Z(z,j,1),this.#Z(this.element,j,1),this.#G.set(j,(this.#G.get(j)??0)+1),this.#M++===0)this.element.setAttribute("aria-busy","true");for(let Q of this.#h(j))this.#Z(Q,j,1)}#Uj(j,z){this.#Z(z,j,-1),this.#Z(this.element,j,-1);let Q=(this.#G.get(j)??1)-1;if(Q<=0)this.#G.delete(j);else this.#G.set(j,Q);if(--this.#M<=0)this.#M=0,this.element.removeAttribute("aria-busy");for(let W of this.#h(j))this.#Z(W,j,-1)}#Z(j,z,Q){if(!j||typeof j.getAttribute!=="function")return;let W=this.#I.get(j)??new Map,X=(W.get(z)??0)+Q;if(X<=0)W.delete(z);else W.set(z,X);if(W.size===0){this.#I.delete(j),j.removeAttribute("data-reactive-busy");return}this.#I.set(j,W),j.setAttribute("data-reactive-busy",[...W.keys()].join(" "))}#h(j){return[...this.element.querySelectorAll?.("[data-reactive-busy-on]")??[]].filter((Q)=>Q.getAttribute("data-reactive-busy-on")===j&&this.#j(Q))}#_j(j,z,Q){if(!Q||!z)return()=>{};let W=this.#Ij(Q,z),X=Array.isArray(Q.class)?Q.class:[],Z=[];for(let G of W){let K=X.filter((Y)=>!G.classList.contains(Y));if(G.classList.add(...K),K.length)Z.push([G,K])}let $=this.#V.get(z);if($)$.count++;else if(Q.disable||Q.text!=null)this.#V.set(z,{count:1,disabled:z.disabled,text:z.textContent,hadText:Q.text!=null});if(Q.disable)z.disabled=!0;if(Q.text!=null)z.textContent=Q.text;return()=>{for(let[G,K]of Z)if(G.isConnected)G.classList.remove(...K);this.#Mj(z,Q)}}#Mj(j,z){let Q=this.#V.get(j);if(!Q)return;if(--Q.count>0)return;if(this.#V.delete(j),!j.isConnected)return;if(z.disable)j.disabled=Q.disabled;if(Q.hadText&&j.textContent===z.text)j.textContent=Q.text}#Ij(j,z){if(j.to==null)return z?[z]:[];return this.#x({to:j.to})}#Nj(){return this.#y??=document.querySelector('meta[name="phlex-reactive-action-path"]')?.content||"/reactive/actions"}#Lj(){if(this.#_!=null)return this.#_;let j=document.querySelector('meta[name="phlex-reactive-timeout"]')?.content,z=Number(j);return this.#_=Number.isFinite(z)&&z>0?z:30000}#Aj(){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{w as registerReactiveVisit,b as registerReactiveToken,f as registerReactiveOffline,h as registerReactiveJs,y as registerReactiveDismiss,S as registerReactiveActions,c as escapeRegExp,u as enableLatencySim,p as disableLatencySim,$j as default,g as checkReactiveRegistration,Xj as __resetReactiveRegistrationForTest,Qj as __resetReactiveOfflineForTest,Wj as __resetReactiveLatencyForTest,zj as __resetReactiveDismissForTest,Zj as __markReactiveConnectedForTest,P as LATENCY_KEY};
1
+ import{Controller as S}from"@hotwired/stimulus";import{confirmResolver as T}from"phlex/reactive/confirm";import{computeReducer as k}from"phlex/reactive/compute";function b(){let j=window.Turbo?.StreamActions;if(!j||j["reactive:visit"])return;j["reactive:visit"]=function(){let z=this.getAttribute("data-url");if(z)window.Turbo.visit(z,{action:"advance"})}}function w(){let j=window.Turbo?.StreamActions;if(!j||j["reactive:token"])return;j["reactive:token"]=function(){let z=this.getAttribute("data-reactive-token-value"),Q=this.getAttribute("target");if(!z||!Q)return;let W=document.getElementById(Q);if(W)W.setAttribute("data-reactive-token-value",z)}}function y(){let j=window.Turbo?.StreamActions;if(!j||j["reactive:js"])return;j["reactive:js"]=function(){let z=B(this.getAttribute("data-reactive-ops"));if(!z.length)return;let Q=this.getAttribute("target"),W=Q?document.getElementById(Q):null;if(Q&&!W)return;R(z,(X)=>t(X,W))}}var O=!1;function h(){if(O)return;if(typeof document>"u"||!document.addEventListener)return;O=!0,document.addEventListener("turbo:before-stream-render",v)}function v(j){let z=j.detail,Q=z?.render;if(typeof Q!=="function"||Q.__reactiveDismissWrapped){if(typeof requestAnimationFrame==="function")requestAnimationFrame(L);else setTimeout(L,0);return}let W=async(X)=>{await Q(X),L()};W.__reactiveDismissWrapped=!0,z.render=W}function L(){let j=document.querySelectorAll("[data-reactive-dismiss-after]");for(let z of j){if(z.hasAttribute("data-reactive-dismiss-scheduled"))continue;let Q=Number(z.getAttribute("data-reactive-dismiss-after"));if(!Number.isFinite(Q)||Q<=0)continue;z.setAttribute("data-reactive-dismiss-scheduled",""),setTimeout(()=>z.remove(),Q)}}function Qj(){O=!1}var x=!1;function f(){if(x)return;if(typeof window>"u"||typeof document>"u")return;if(typeof window.addEventListener!=="function")return;x=!0;let j=()=>{let z=document.documentElement;if(typeof z?.toggleAttribute!=="function")return;z.toggleAttribute("data-reactive-offline",globalThis.navigator?.onLine===!1)};j(),window.addEventListener("online",j),window.addEventListener("offline",j)}function Wj(){x=!1}var P="phlex-reactive:latency",M=!1;function u(j){if(typeof sessionStorage>"u")return;sessionStorage.setItem(P,String(j))}function p(){if(typeof sessionStorage>"u")return;sessionStorage.removeItem(P),M=!1}function m(){if(typeof window>"u"||typeof document>"u")return;if(document.querySelector?.('meta[name="phlex-reactive-env"]')?.content!=="development")return;window.PhlexReactive={enableLatencySim:u,disableLatencySim:p}}function Xj(){M=!1}function F(){b(),w(),y(),h(),f(),m()}function c(j){return j.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}if(typeof window<"u")if(window.Turbo)F();else document.addEventListener("turbo:load",F,{once:!0});var N=!1;function g(){if(N)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 Zj(){N=!1}function $j(){N=!0}if(typeof window<"u"&&typeof document<"u"){let j=()=>setTimeout(g,0);if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",j,{once:!0});else j()}var d=new Set(["href","src","srcdoc","action","formaction","xlink:href","style"]);function l(j){let z=String(j).toLowerCase();return z.startsWith("on")||d.has(z)}function s(j,z,Q){let[W,X,Z]=z;j.classList.add(W,X),Q(),requestAnimationFrame(()=>{j.classList.remove(X),j.classList.add(Z)});let $=!1,G=()=>{if($)return;$=!0,j.classList.remove(W,Z)};j.addEventListener("animationend",G,{once:!0}),setTimeout(G,350)}var C=Object.freeze({show:(j,z)=>A(j,!1,z),hide:(j,z)=>A(j,!0,z),toggle:(j,z)=>A(j,!j.hidden,z),add_class:(j,z)=>j.classList.add(...z.classes??[]),remove_class:(j,z)=>j.classList.remove(...z.classes??[]),toggle_class:(j,z)=>(z.classes??[]).forEach((Q)=>j.classList.toggle(Q)),set_attr:(j,z)=>{if(D(z.name))j.setAttribute(z.name,z.value??"")},remove_attr:(j,z)=>{if(D(z.name))j.removeAttribute(z.name)},toggle_attr:(j,z)=>{if(!D(z.name))return;if(j.hasAttribute(z.name))j.removeAttribute(z.name);else j.setAttribute(z.name,"")},focus:(j)=>j.focus?.(),focus_first:(j)=>i(j)?.focus?.(),text:(j,z)=>{let Q=String(z.value??"");if(j.textContent!==Q)j.textContent=Q},dispatch:(j,z)=>{j.dispatchEvent(new CustomEvent(z.name,{bubbles:!0,composed:!0,detail:z.detail??{}}))}});function A(j,z,Q){if(Q?.transition)s(j,Q.transition,()=>j.hidden=z);else j.hidden=z}function D(j){if(!l(j))return!0;return console.warn(`[phlex-reactive] refused client attr op on ${JSON.stringify(j)} — skipped`),!1}var o=/^#[A-Za-z_][\w-]*$/;function n(j){if(typeof j==="string"&&o.test(j))return!0;return console.warn(`[phlex-reactive] refused cross-root mirror target ${JSON.stringify(j)} — skipped`),!1}function r(j,z){let Q=j.getAttribute("data-reactive-show-equals");if(Q!==null)return z===Q;let W=j.getAttribute("data-reactive-show-not");if(W!==null)return z!==W;let X=j.getAttribute("data-reactive-show-in");if(X!==null){try{let Z=JSON.parse(X);if(Array.isArray(Z))return Z.includes(z)}catch{}return console.warn(`[phlex-reactive] malformed reactive_show in: list ${JSON.stringify(X)} — skipped`),null}return console.warn("[phlex-reactive] a reactive_show binding declares no predicate — skipped"),null}var a='a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';function i(j){return j.querySelectorAll?.(a)?.[0]??null}function B(j){if(Array.isArray(j))return j;if(typeof j!=="string")return[];try{let z=JSON.parse(j);return Array.isArray(z)?z:[]}catch{return[]}}function R(j,z){for(let Q of j){if(!Array.isArray(Q))continue;let[W,X={}]=Q;if(!Object.hasOwn(C,W)){console.warn(`[phlex-reactive] unknown client op ${JSON.stringify(W)} — skipped`);continue}for(let Z of z(X))C[W](Z,X)}}function t(j,z){let Q=j.to;if(z){if(Q==="@root")return[z];if(typeof Q!=="string"||Q==="")return[];if(j.global)return[...document.querySelectorAll(Q)];return[...z.querySelectorAll(Q)]}if(typeof Q!=="string"||Q===""||Q==="@root")return[];return[...document.querySelectorAll(Q)]}class Gj extends S{static values={token:String};#E;#H=new Map;#G=new Map;#f;#M;#F;#N=0;#J=new Map;#L=new WeakMap;#V=new Map;#K;#q;#Y;#j;connect(){if(N=!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.#u()){if(this.#K=()=>this.#O(),this.element.addEventListener?.("turbo:morph-element",this.#K),this.#O(),this.element.getAttribute?.("data-reactive-warn-unsaved")==="true")this.#Zj()}if(this.#Gj())this.#j=()=>this.#y(),this.element.addEventListener?.("input",this.#j),this.element.addEventListener?.("change",this.#j),this.element.addEventListener?.("turbo:morph-element",this.#j),this.#y()}#u(){if((this.element.getAttribute?.("data-action")??"").includes("reactive#trackDirty"))return!0;let j=this.element.querySelectorAll?.('[data-action*="reactive#trackDirty"]')??[];for(let z of j)if(this.#z(z))return!0;return!1}disconnect(){this.#s(),this.#n(),this.#$j(),this.#Kj()}dispatch(j){let{action:z,params:Q,debounce:W,throttle:X,confirm:Z,outside:$,window:G,optimistic:q,loading:Y}=j.params;if(!z)return;if($&&this.element.contains(j.target))return;let H=j.currentTarget??j.target;if(!G&&!this.#Uj(q,H))j.preventDefault();if(!Z)return this.#S(H,z,Q,W,X,q,Y);Promise.resolve().then(()=>T(Z)).catch(()=>!1).then((J)=>{if(J)this.#S(H,z,Q,W,X,q,Y)})}runOps(j){let{ops:z,outside:Q,window:W}=j.params;if(Q&&this.element.contains(j.target))return;if(!W)j.preventDefault();this.#Vj(this.#Hj(z))}trackDirty(){this.#O()}recompute(j){let z=this.#m(),Q=z.map(([J])=>J),W=this.#I(),X=new Map,Z=(J)=>{if(X.has(J))return X.get(J);let K=null;for(let U of this.element.querySelectorAll(`[name="${J}"]`))if(W(U)){K=U;break}return X.set(J,K),K};for(let J of Q)this.#B(J,Z(J)?.value??"");let $=this.element.getAttribute("data-reactive-compute-reducer-param"),G=$?k($):null;if(!G){this.#R({},Z);return}let q=this.#p("data-reactive-compute-outputs-param"),Y={};for(let[J,K]of z){let U=Z(J);if(K==="string")Y[J]=U?.value??"";else{let _=Number(U?.value);Y[J]=Number.isFinite(_)?_:0}}let H=G(Y,{changed:this.#c(j,Q)})||{};for(let J of q){if(!(J in H))continue;let K=Z(J);if(K){if(String(H[J])===K.value)continue;K.value=H[J],K.dispatchEvent(new Event("input",{bubbles:!0}))}else this.#B(J,H[J])}this.#R(H,Z)}listnavNext(j){this.#C(j,1)}listnavPrev(j){this.#C(j,-1)}listnavPick(j){let z=this.#A(j),Q=z.findIndex((W)=>W.hasAttribute("data-reactive-highlighted"));if(Q<0)return;j.preventDefault(),z[Q].click()}listnavClose(j){for(let z of this.#A(j))z.removeAttribute("data-reactive-highlighted")}#C(j,z){let Q=this.#A(j);if(!Q.length)return;j.preventDefault();let W=Q.findIndex(($)=>$.hasAttribute("data-reactive-highlighted")),X=W<0?z>0?0:Q.length-1:(W+z+Q.length)%Q.length;for(let $ of Q)$.removeAttribute("data-reactive-highlighted");let Z=Q[X];Z.setAttribute("data-reactive-highlighted","true"),Z.scrollIntoView?.({block:"nearest"})}#A(j){let Q=(j?.currentTarget??j?.target??this.element).getAttribute?.("data-reactive-listnav-option-param")??this.element.getAttribute("data-reactive-listnav-option-param");if(!Q)return[];let W=this.#I();return Array.from(this.element.querySelectorAll(Q)).filter(W)}#p(j){let z=this.element.getAttribute(j);if(!z)return[];try{let Q=JSON.parse(z);return Array.isArray(Q)?Q:[]}catch{return[]}}#m(){let j=this.element.getAttribute("data-reactive-compute-inputs-param");if(!j)return[];try{let z=JSON.parse(j);if(Array.isArray(z))return z.map((Q)=>[Q,"number"]);if(z&&typeof z==="object")return Object.entries(z);return[]}catch{return[]}}#c(j,z){let Q=j?.target;if(!Q?.name||typeof Q.closest!=="function")return null;if(!z.includes(Q.name))return null;return this.#z(Q)?Q.name:null}#B(j,z){let Q=String(z);for(let W of this.#g(j)){if(W.textContent===Q)continue;W.textContent=Q}}#g(j){let z=this.element.querySelectorAll(`[data-reactive-text="${j}"]`);return Array.from(z).filter((Q)=>this.#z(Q))}#R(j,z){let Q=this.#d();for(let[W,X]of Object.entries(Q)){let Z=W in j?j[W]:z(W)?.value;if(Z===void 0||Z===null)continue;let $=String(Z);for(let G of Array.isArray(X)?X:[X]){if(!n(G))continue;for(let q of document.querySelectorAll(G)){if(q.textContent===$)continue;q.textContent=$}}}}#d(){let j=this.element.getAttribute("data-reactive-compute-mirror-param");if(!j)return{};try{let z=JSON.parse(j);return z&&typeof z==="object"&&!Array.isArray(z)?z:{}}catch{return{}}}#S(j,z,Q,W,X,Z,$){if(this.#_("reactive:before-dispatch",{action:z,params:this.#h(Q),element:this.element},{cancelable:!0}).defaultPrevented)return;let q=Number(W)||0;if(q>0)return this.#l(j,q,z,Q,Z,$);let Y=Number(X)||0;if(Y>0)return this.#o(j,Y,z,Q,Z,$);return this.#U(z,Q,Z,j,$)}#U(j,z,Q,W,X){let Z=this.#_j(Q,W),$=this.#Mj(j,W,X);return this.queue=(this.queue??Promise.resolve()).then(()=>this.#zj(j,z,Z,$)),this.queue}#l(j,z,Q,W,X,Z){this.#D(j);let $=()=>{this.#D(j),this.#U(Q,W,X,j,Z)},G=setTimeout($,z);j?.addEventListener?.("blur",$,{once:!0}),this.#H.set(j,{timer:G,flush:$})}#D(j){let z=this.#H.get(j);if(!z)return;clearTimeout(z.timer),j?.removeEventListener?.("blur",z.flush),this.#H.delete(j)}#s(){for(let j of[...this.#H.keys()])this.#D(j)}#o(j,z,Q,W,X,Z){let $=this.#G.get(j)??new Map;if($.has(Q))return;let G=setTimeout(()=>{if($.delete(Q),$.size===0)this.#G.delete(j)},z);return $.set(Q,G),this.#G.set(j,$),this.#U(Q,W,X,j,Z)}#n(){for(let j of this.#G.values())for(let z of j.values())clearTimeout(z);this.#G.clear()}#_(j,z,{cancelable:Q=!1}={}){let W=new CustomEvent(j,{bubbles:!0,composed:!0,cancelable:Q,detail:z});return(this.element.isConnected?this.element:document).dispatchEvent(W),W}#Q(j,z,Q,W){let X=()=>{if(!this.element.isConnected){console.warn("[phlex-reactive] retry() ignored — the reactive root left the DOM");return}return this.#U(j,z)};this.#_("reactive:error",{action:j,params:Q,...W,retry:X})}#W(j){if(this.element?.isConnected===!1)return;this.element?.setAttribute?.("data-reactive-error",j)}#r(){this.element?.removeAttribute?.("data-reactive-error")}#a(){let j=document.querySelector("[data-reactive-error-flash]");if(!j?.content)return;let z=j.getAttribute("data-reactive-error-flash")||"flash",Q=document.getElementById(z);if(!Q)return;Q.appendChild(j.content.cloneNode(!0))}#i(){if(typeof sessionStorage>"u")return Promise.resolve();let j=Number(sessionStorage.getItem(P));if(!Number.isFinite(j)||j<=0)return Promise.resolve();if(!M)M=!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((z)=>setTimeout(z,j))}#t(){return this.element?.getAttribute?.("data-reactive-debug")==="true"}#T(){return typeof performance<"u"&&typeof performance.now==="function"?performance.now():Date.now()}#e(j){if(!j)return[];let z=[],Q=/<turbo-stream\b([^>]*)>/g,W;while((W=Q.exec(j))!==null){let X=W[1],Z=X.match(/\baction="([^"]*)"/)?.[1]??"?",$=X.match(/\btarget="([^"]*)"/)?.[1];z.push($?`${Z} → #${$}`:Z)}return z}#jj(j){let{action:z,status:Q,ms:W}=j,Z=`reactive ${this.element?.id?`#${this.element.id} `:""}${z} → ${Q??"—"} (${Math.round(W)}ms)`;if(console.groupCollapsed(Z),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#zj(j,z,Q,W){let{fields:X,files:Z}=this.#Wj(),$=this.#h(z),G={...X,...$},q=this.#X,Y=Z.length>0,H=Y?this.#qj(q,j,G,Z):JSON.stringify({token:q,act:j,params:G}),J=this.#t()?{action:j,paramNames:Object.keys($),fieldNames:Object.keys(X),encoding:Y?"multipart":"json",status:null,streams:[],tokenRefreshed:!1,started:this.#T()}:null;await this.#i();try{if(navigator.onLine===!1){this.#Z(Q),this.#W("offline"),this.#Q(j,z,G,{kind:"offline"});return}let K;try{let V={Accept:"text/vnd.turbo-stream.html","X-CSRF-Token":this.#Ej()};if(!Y)V["Content-Type"]="application/json";let I=this.#Fj();if(I)V["X-Pgbus-Connection"]=I;K=await fetch(this.#xj(),{method:"POST",headers:V,body:H,credentials:"same-origin",signal:AbortSignal.timeout(this.#Pj())})}catch(V){if(console.error("[phlex-reactive] action error",V),this.#Z(Q),V?.name==="TimeoutError"||V?.name==="AbortError"){this.#W("timeout"),this.#Q(j,z,G,{kind:"timeout"});return}this.#a(),this.#W("network"),this.#Q(j,z,G,{kind:"network"});return}if(J)J.status=K.status;if(K.redirected){console.error("[phlex-reactive] action was redirected (auth/CSRF?) — no update applied"),this.#Z(Q),this.#W("redirected"),this.#Q(j,z,G,{kind:"redirected",status:K.status});return}if(!K.ok){let V=await K.text();if(console.error(`[phlex-reactive] action failed: HTTP ${K.status}`,V),this.#Z(Q),(K.headers.get("Content-Type")||"").includes("turbo-stream")){let I=this.#b(V);if(this.#X=I??this.#X,J)this.#k(J,V,I);window.Turbo.renderStreamMessage(V)}this.#W("http"),this.#Q(j,z,G,{kind:"http",status:K.status,body:V});return}let U=K.headers.get("Content-Type")||"";if(!U.includes("turbo-stream")){console.error(`[phlex-reactive] expected a turbo-stream, got "${U}" — no update applied`),this.#Z(Q),this.#W("content-type"),this.#Q(j,z,G,{kind:"content-type",status:K.status});return}let _=await K.text(),E=this.#b(_);if(this.#X=E??this.#X,J)this.#k(J,_,E);window.Turbo.renderStreamMessage(_),this.#r(),this.#_("reactive:applied",{action:j,params:G,html:_})}catch(K){console.error("[phlex-reactive] action error",K),this.#Z(Q),this.#_("reactive:error",{action:j,params:G,kind:"apply"})}finally{if(W?.(),J)this.#jj({...J,ms:this.#T()-J.started})}}#k(j,z,Q){j.streams=this.#e(z),j.tokenRefreshed=Q!=null}get#X(){return this.#E??this.tokenValue}set#X(j){this.#E=j}#b(j){let z=this.element.id;if(!z)return j.match(/data-reactive-token-value="([^"]+)"/)?.[1];let{token:Q,self:W}=this.#Qj(z),X=j.match(Q);if(X)return X[1];let Z=j.match(W);if(Z)return Z[1].match(/data-reactive-token-value="([^"]+)"/)?.[1];return}#Qj(j){let z=this.#F;if(z&&z.id===j)return z;let Q=c(j);return this.#F={id:j,token:new RegExp(`<turbo-stream\\b[^>]*\\baction="reactive:token"[^>]*\\btarget="${Q}"[^>]*\\bdata-reactive-token-value="([^"]+)"`),self:new RegExp(`<turbo-stream\\b[^>]*\\baction="(?:replace|update)"[^>]*\\btarget="${Q}"[^>]*>([\\s\\S]*?)</turbo-stream>`)}}#z(j){return j.closest('[data-controller~="reactive"]')===this.element}#I(){if(this.element.querySelectorAll('[data-controller~="reactive"]').length===0)return()=>!0;return(z)=>this.#z(z)}#Wj(){let j={},z=[],Q=this.#I();return this.element.querySelectorAll("input[name], select[name], textarea[name]").forEach((W)=>{if(!Q(W))return;if(W.type==="file")for(let X of W.files??[])z.push({name:W.name,file:X,multiple:W.multiple});else if(W.type==="checkbox")j[W.name]=W.checked;else if(W.type==="radio"){if(W.checked)j[W.name]=W.value}else j[W.name]=W.value}),this.element.querySelectorAll("[name]:is(lexxy-editor, trix-editor, [contenteditable=''], [contenteditable=true], [contenteditable=plaintext-only])").forEach((W)=>{if(!Q(W))return;let X=W.getAttribute("name");if(!X)return;let Z=j[X];if(Z==null||Z==="")j[X]=W.value??W.textContent??W.innerHTML??""}),{fields:j,files:z}}#O(){if(typeof this.element?.querySelectorAll!=="function")return;let j=0;if(this.element.querySelectorAll("input[name], select[name], textarea[name]").forEach((z)=>{if(!this.#z(z))return;if(z.type==="file")return;if(this.#Xj(z))z.setAttribute("data-reactive-dirty","true"),j++;else z.removeAttribute("data-reactive-dirty")}),j>0)this.element.setAttribute("data-reactive-dirty",String(j));else this.element.removeAttribute("data-reactive-dirty")}#Xj(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((z)=>z.selected!==z.defaultSelected);return j.value!==j.defaultValue}#w(){let j=this.element.getAttribute?.("data-reactive-dirty"),z=Number(j);return Number.isFinite(z)&&z>0?z:0}#Zj(){if(typeof window>"u"||typeof window.addEventListener!=="function")return;this.#q=(j)=>{if(this.#w()===0)return;return j.preventDefault(),j.returnValue="You have unsaved changes.",j.returnValue},this.#Y=(j)=>{if(this.#w()===0)return;if(!(typeof window.confirm==="function"?window.confirm("You have unsaved changes. Leave anyway?"):!0))j.preventDefault?.()},window.addEventListener("beforeunload",this.#q),window.addEventListener("turbo:before-visit",this.#Y)}#$j(){if(this.#K)this.element.removeEventListener?.("turbo:morph-element",this.#K),this.#K=void 0;if(typeof window<"u"&&typeof window.removeEventListener==="function"){if(this.#q)window.removeEventListener("beforeunload",this.#q);if(this.#Y)window.removeEventListener("turbo:before-visit",this.#Y)}this.#q=void 0,this.#Y=void 0}#Gj(){let j=this.element.querySelectorAll?.("[data-reactive-show-field]")??[];for(let z of j)if(this.#z(z))return!0;return!1}#y(){if(typeof this.element?.querySelectorAll!=="function")return;let j=this.#I(),z=new Map;for(let Q of this.element.querySelectorAll("[data-reactive-show-field]")){if(!j(Q))continue;let W=Q.getAttribute("data-reactive-show-field");if(!W)continue;if(!z.has(W))z.set(W,this.#Jj(W,j));let X=z.get(W);if(X===null)continue;let Z=r(Q,X);if(Z===null)continue;Q.hidden=!Z}}#Jj(j,z){let Q=!1,W=null;for(let X of this.element.querySelectorAll(`[name="${j}"]`)){if(!z(X))continue;if(X.type==="checkbox")return X.checked?"true":"false";if(X.type==="radio"){if(X.checked)return X.value??"";Q=!0;continue}W??=X}if(W)return W.value??"";return Q?"":null}#Kj(){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}#qj(j,z,Q,W){let X=new FormData;X.append("token",j),X.append("act",z);for(let[$,G]of Object.entries(Q))this.#x(X,`params[${$}]`,G);let Z=this.#Yj(W);for(let{name:$,file:G,multiple:q}of W){let H=q||Z.has($)?`params[${$}][]`:`params[${$}]`;X.append(H,G,G.name)}return X}#x(j,z,Q){if(Q==null)j.append(z,"");else if(Array.isArray(Q))Q.forEach((W,X)=>this.#x(j,`${z}[${X}]`,W));else if(typeof Q==="object")for(let[W,X]of Object.entries(Q))this.#x(j,`${z}[${W}]`,X);else j.append(z,String(Q))}#Yj(j){let z=new Map;for(let{name:Q}of j)z.set(Q,(z.get(Q)??0)+1);return new Set([...z].filter(([,Q])=>Q>1).map(([Q])=>Q))}#h(j){if(!j)return{};try{return typeof j==="string"?JSON.parse(j):j}catch{return{}}}#Hj(j){return B(j)}#Vj(j){R(j,(z)=>this.#P(z))}#P(j){let z=j.to;if(z==="@root")return[this.element];if(typeof z!=="string"||z==="")return[];if(j.global)return[...document.querySelectorAll(z)];return[...this.element.querySelectorAll(z)].filter((Q)=>this.#z(Q))}#Uj(j,z){if(j?.checked!=="keep")return!1;let Q=z?.type;return Q==="checkbox"||Q==="radio"}#_j(j,z){if(!j)return null;let Q=this.#Ij(j,z),W=[];for(let X of Q){if(j.add_class){let Z=j.add_class.filter(($)=>!X.classList.contains($));if(X.classList.add(...Z),Z.length)W.push(()=>X.classList.remove(...Z))}if(j.remove_class){let Z=j.remove_class.filter(($)=>X.classList.contains($));if(X.classList.remove(...Z),Z.length)W.push(()=>X.classList.add(...Z))}if(j.toggle_class)j.toggle_class.forEach((Z)=>X.classList.toggle(Z)),W.push(()=>j.toggle_class.forEach((Z)=>X.classList.toggle(Z)));if(j.hide)X.hidden=!0,W.push(()=>X.hidden=!1)}if(j.checked==="keep"&&z&&"checked"in z){let X=z.checked;W.push(()=>z.checked=!X)}return W.length?W:null}#Z(j){if(!j)return;if(!this.element.isConnected)return;for(let z of j)z()}#Ij(j,z){if(j.to==null)return z?[z]:[];return this.#P({to:j.to})}#Mj(j,z,Q){this.#Nj(j,z);let W=this.#Aj(j,z,Q),X=!1;return()=>{if(X)return;X=!0,this.#Lj(j,z),W()}}#Nj(j,z){if(this.#$(z,j,1),this.#$(this.element,j,1),this.#J.set(j,(this.#J.get(j)??0)+1),this.#N++===0)this.element.setAttribute("aria-busy","true");for(let Q of this.#v(j))this.#$(Q,j,1)}#Lj(j,z){this.#$(z,j,-1),this.#$(this.element,j,-1);let Q=(this.#J.get(j)??1)-1;if(Q<=0)this.#J.delete(j);else this.#J.set(j,Q);if(--this.#N<=0)this.#N=0,this.element.removeAttribute("aria-busy");for(let W of this.#v(j))this.#$(W,j,-1)}#$(j,z,Q){if(!j||typeof j.getAttribute!=="function")return;let W=this.#L.get(j)??new Map,X=(W.get(z)??0)+Q;if(X<=0)W.delete(z);else W.set(z,X);if(W.size===0){this.#L.delete(j),j.removeAttribute("data-reactive-busy");return}this.#L.set(j,W),j.setAttribute("data-reactive-busy",[...W.keys()].join(" "))}#v(j){return[...this.element.querySelectorAll?.("[data-reactive-busy-on]")??[]].filter((Q)=>Q.getAttribute("data-reactive-busy-on")===j&&this.#z(Q))}#Aj(j,z,Q){if(!Q||!z)return()=>{};let W=this.#Oj(Q,z),X=Array.isArray(Q.class)?Q.class:[],Z=[];for(let G of W){let q=X.filter((Y)=>!G.classList.contains(Y));if(G.classList.add(...q),q.length)Z.push([G,q])}let $=this.#V.get(z);if($)$.count++;else if(Q.disable||Q.text!=null)this.#V.set(z,{count:1,disabled:z.disabled,text:z.textContent,hadText:Q.text!=null});if(Q.disable)z.disabled=!0;if(Q.text!=null)z.textContent=Q.text;return()=>{for(let[G,q]of Z)if(G.isConnected)G.classList.remove(...q);this.#Dj(z,Q)}}#Dj(j,z){let Q=this.#V.get(j);if(!Q)return;if(--Q.count>0)return;if(this.#V.delete(j),!j.isConnected)return;if(z.disable)j.disabled=Q.disabled;if(Q.hadText&&j.textContent===z.text)j.textContent=Q.text}#Oj(j,z){if(j.to==null)return z?[z]:[];return this.#P({to:j.to})}#xj(){return this.#f??=document.querySelector('meta[name="phlex-reactive-action-path"]')?.content||"/reactive/actions"}#Pj(){if(this.#M!=null)return this.#M;let j=document.querySelector('meta[name="phlex-reactive-timeout"]')?.content,z=Number(j);return this.#M=Number.isFinite(z)&&z>0?z:30000}#Ej(){return document.querySelector('meta[name="csrf-token"]')?.content??""}#Fj(){return document.querySelector("pgbus-stream-source[connection-id]")?.getAttribute("connection-id")||document.querySelector('meta[name="pgbus-connection-id"]')?.content||null}}export{b as registerReactiveVisit,w as registerReactiveToken,f as registerReactiveOffline,y as registerReactiveJs,h as registerReactiveDismiss,F as registerReactiveActions,c as escapeRegExp,u as enableLatencySim,p as disableLatencySim,Gj as default,g as checkReactiveRegistration,Zj as __resetReactiveRegistrationForTest,Wj as __resetReactiveOfflineForTest,Xj as __resetReactiveLatencyForTest,Qj as __resetReactiveDismissForTest,$j as __markReactiveConnectedForTest,P as LATENCY_KEY};
2
2
 
3
- //# debugId=9EC165A88E302A8264756E2164756E21
3
+ //# debugId=B0542866EC05173F64756E2164756E21
4
4
  //# sourceMappingURL=reactive_controller.min.js.map