phlex-reactive 0.12.2 → 0.12.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.
@@ -1092,8 +1092,25 @@ const CLIENT_OPS = Object.freeze({
1092
1092
  dispatch: (el, args) => {
1093
1093
  el.dispatchEvent(new CustomEvent(args.name, { bubbles: true, composed: true, detail: args.detail ?? {} }))
1094
1094
  },
1095
+
1096
+ // Submit the target's OWN form (issue #226) via requestSubmit() — constraint
1097
+ // validation runs and a REAL cancelable `submit` event fires, so an
1098
+ // on(:action, event: "submit") interception or a native/Turbo form handles it
1099
+ // exactly like a user submit. No form → no-op. ACTOR-ONLY like focus: the
1100
+ // broadcast builder refuses it server-side (BROADCAST_REFUSED_OPS).
1101
+ submit: (el) => submitFormFor(el)?.requestSubmit?.(),
1095
1102
  })
1096
1103
 
1104
+ // The form a submit op commits (issue #226), in order: the target itself when
1105
+ // it IS a form (tagName, not instanceof — fake-node/test friendly), its form
1106
+ // owner for a control (input.form — honors a form= attribute), else the nearest
1107
+ // ancestor form. closest() may cross the component root by design — the
1108
+ // FIELD'S OWN form is the submit scope, not the reactive boundary.
1109
+ function submitFormFor(el) {
1110
+ if (el?.tagName === "FORM") return el
1111
+ return el?.form ?? el?.closest?.("form") ?? null
1112
+ }
1113
+
1097
1114
  // Apply a hidden-flag change, optionally animated by a [during, from, to]
1098
1115
  // transition (issue #96). Split out so show/hide/toggle share it.
1099
1116
  function setHidden(el, hidden, args) {
@@ -1165,6 +1182,39 @@ function showBindingMatches(el, value) {
1165
1182
  // present. Each coerces BOTH sides to Number and compares.
1166
1183
  const SHOW_NUMERIC_KEYS = ["gte", "gt", "lte", "lt"]
1167
1184
 
1185
+ // The length predicate keys (issue #226) — the client half of Ruby's
1186
+ // ShowConditions::LENGTH_KEYS. Length is counted in CODEPOINTS
1187
+ // ([...str].length), NOT UTF-16 code units (str.length), so Ruby's
1188
+ // String#length and this evaluator agree on multibyte values — the shared
1189
+ // fixture's emoji vector proves it.
1190
+ const SHOW_LENGTH_KEYS = ["len_eq", "len_gte", "len_gt", "len_lte", "len_lt"]
1191
+
1192
+ // Evaluate one length predicate. Length is a TOTAL function (blank/absent →
1193
+ // 0), so every field value is decidable — no fail-closed special case like the
1194
+ // numeric thresholds ({ length: 0 } legitimately matches a blank field). A
1195
+ // non-Integer LITERAL is a malformed binding — warn-skip (null), default-deny.
1196
+ function lengthPredicateMatches(key, literal, value) {
1197
+ if (!Number.isInteger(literal)) {
1198
+ console.warn(`[phlex-reactive] reactive_show ${key}: needs an integer literal, got ${JSON.stringify(literal)} — skipped`)
1199
+ return null
1200
+ }
1201
+ const length = [...String(value ?? "")].length
1202
+ switch (key) {
1203
+ case "len_eq":
1204
+ return length === literal
1205
+ case "len_gte":
1206
+ return length >= literal
1207
+ case "len_gt":
1208
+ return length > literal
1209
+ case "len_lte":
1210
+ return length <= literal
1211
+ case "len_lt":
1212
+ return length < literal
1213
+ default:
1214
+ return null
1215
+ }
1216
+ }
1217
+
1168
1218
  // Evaluate one numeric threshold predicate against a field value. Returns
1169
1219
  // true/false for a decidable comparison, or null when the LITERAL itself is
1170
1220
  // non-numeric (a malformed binding — warn-skip, default-deny). A non-numeric
@@ -1218,6 +1268,10 @@ function showPredicateMatches(pred, value) {
1218
1268
  for (const key of SHOW_NUMERIC_KEYS) {
1219
1269
  if (key in pred) return numericPredicateMatches(key, pred[key], value)
1220
1270
  }
1271
+ // Length predicates (issue #226): codepoint count vs an Integer literal.
1272
+ for (const key of SHOW_LENGTH_KEYS) {
1273
+ if (key in pred) return lengthPredicateMatches(key, pred[key], value)
1274
+ }
1221
1275
  return null
1222
1276
  }
1223
1277
 
@@ -1242,6 +1296,27 @@ function parseShowCompound(raw) {
1242
1296
  return null
1243
1297
  }
1244
1298
 
1299
+ // Parse a data-reactive-on-complete payload (issue #226): a JSON array of
1300
+ // { any: [[term, …], …], ops: [[op, args], …] } bindings. Malformed JSON, a
1301
+ // non-array, or a binding missing either half degrades to [] WITH a warn — a
1302
+ // bad payload must never throw or fire an op (client-side default-deny, the
1303
+ // parseShowCompound posture).
1304
+ function parseOnComplete(raw) {
1305
+ try {
1306
+ const list = JSON.parse(raw)
1307
+ if (
1308
+ Array.isArray(list) &&
1309
+ list.every((b) => b && typeof b === "object" && Array.isArray(b.any) && Array.isArray(b.ops))
1310
+ ) {
1311
+ return list
1312
+ }
1313
+ } catch {
1314
+ // fall through to the warn
1315
+ }
1316
+ console.warn(`[phlex-reactive] malformed reactive_on_complete payload ${JSON.stringify(raw)} — skipped`)
1317
+ return []
1318
+ }
1319
+
1245
1320
  // Evaluate one DNF TERM against a resolved field value (issue #180). A missing
1246
1321
  // field (null) or a malformed/unknown predicate folds to FALSE — fail-closed
1247
1322
  // (default-deny): a broken AND term can't pass, a broken OR term can't reveal.
@@ -1348,6 +1423,20 @@ function parseOps(raw) {
1348
1423
  }
1349
1424
  }
1350
1425
 
1426
+ // Normalize a reducer's reserved $ops output (issue #226): the compute `ops`
1427
+ // builder (its .ops list), a raw [[name, args], ...] array, or null/undefined
1428
+ // (no effect this pass). An EMPTY list is the same as null — "nothing to run"
1429
+ // must not latch the rising edge. Anything else warns + is dropped
1430
+ // (default-deny, like every other malformed op source). Reducers are trusted
1431
+ // app code, but their ops still run through the frozen CLIENT_OPS whitelist.
1432
+ function computeOpsList(raw) {
1433
+ if (raw == null) return null
1434
+ const list = Array.isArray(raw) ? raw : raw.ops
1435
+ if (Array.isArray(list)) return list.length > 0 ? list : null
1436
+ console.warn("[phlex-reactive] $ops must be an ops chain or a [[op, args], ...] list — skipped")
1437
+ return null
1438
+ }
1439
+
1351
1440
  // Interpret a [[name, args], ...] op list against the frozen CLIENT_OPS
1352
1441
  // whitelist (issues #95/#96/#97). `resolveTargets(args)` returns the element(s)
1353
1442
  // an op applies to — the controller scopes to its root (excluding nested
@@ -1416,6 +1505,13 @@ export default class extends Controller {
1416
1505
  // (single-pass write set). Per-instance, so another root's events are never
1417
1506
  // swallowed. WeakSet: entries drop when the short-lived Event is GC'd.
1418
1507
  #computeSelfDispatched = new WeakSet()
1508
+ // Issue #226: the serialized $ops chain of the LAST reducer pass (null when
1509
+ // absent) — the rising-edge latch, keyed on CONTENT: an identical chain
1510
+ // never re-fires (a capped extra keystroke can't re-submit), while a chain
1511
+ // that CHANGED fires again (a multi-box reducer advancing focus box-by-box
1512
+ // emits a different focus target per digit). The seed pass arms this
1513
+ // without firing.
1514
+ #computeOpsSignature = null
1419
1515
  // Dirty tracking (issue #103): the bound re-scan (turbo:morph-element) and the
1420
1516
  // navigate-away guard handlers, held so disconnect() can remove exactly them.
1421
1517
  #boundScanDirty
@@ -1424,6 +1520,15 @@ export default class extends Controller {
1424
1520
  // Show bindings (issue #161): the ONE delegated sync handler shared by the
1425
1521
  // root's input/change/turbo:morph-element listeners, held for teardown.
1426
1522
  #boundSyncShow
1523
+ // Completion bindings (issue #226): the delegated gesture handler + the
1524
+ // no-gesture morph arm, held for teardown; the per-binding rising-edge
1525
+ // latches; and the raw-attr memo that re-parses (and resets the latches)
1526
+ // when a morph rewrites the payload.
1527
+ #boundSyncOnComplete
1528
+ #boundArmOnComplete
1529
+ #onCompleteRaw
1530
+ #onCompleteParsed
1531
+ #onCompleteStates
1427
1532
  // Option filtering (issue #163): the ONE delegated sync handler shared by the
1428
1533
  // root's input/turbo:morph-element listeners, held for teardown.
1429
1534
  #boundSyncFilter
@@ -1532,6 +1637,26 @@ export default class extends Controller {
1532
1637
  this.#syncShow()
1533
1638
  }
1534
1639
 
1640
+ // Completion bindings (issue #226) — ONLY when the root declares
1641
+ // data-reactive-on-complete (the show/filter gate precedent). ONE
1642
+ // delegated input+change listener evaluates every binding's DNF over the
1643
+ // owned fields and runs its ops on the RISING EDGE — the event-driven
1644
+ // flip to true. The connect pass ARMS without firing (a fresh render with
1645
+ // already-satisfied conditions must never self-fire — the $ops seed
1646
+ // precedent), and turbo:morph-element re-arms the same way after an
1647
+ // in-place morph. Listeners added HERE run after the Stimulus-wired
1648
+ // recompute delegation for the same event, so the evaluation reads
1649
+ // compute-NORMALIZED values (and a compute output write dispatches a real
1650
+ // input event that re-evaluates anyway).
1651
+ if (this.#onCompleteEnabled()) {
1652
+ this.#boundSyncOnComplete = (event) => this.#syncOnComplete(event)
1653
+ this.#boundArmOnComplete = () => this.#syncOnComplete(null)
1654
+ this.element.addEventListener?.("input", this.#boundSyncOnComplete)
1655
+ this.element.addEventListener?.("change", this.#boundSyncOnComplete)
1656
+ this.element.addEventListener?.("turbo:morph-element", this.#boundArmOnComplete)
1657
+ this.#syncOnComplete(null)
1658
+ }
1659
+
1535
1660
  // Option filtering (issue #163) — ONLY when the root declares the binding
1536
1661
  // (reactive_filter emits both attrs together), so a component without one
1537
1662
  // pays two attribute reads. ONE delegated input listener on the root — the
@@ -1635,6 +1760,7 @@ export default class extends Controller {
1635
1760
  this.#clearAllThrottles()
1636
1761
  this.#teardownDirtyTracking()
1637
1762
  this.#teardownShowSync()
1763
+ this.#teardownOnCompleteSync()
1638
1764
  this.#teardownFilterSync()
1639
1765
  this.#teardownTagsSync()
1640
1766
  this.#teardownNestedJsonSync()
@@ -1928,7 +2054,12 @@ export default class extends Controller {
1928
2054
  // snapshot above (issue #183): its result drives the whole single-pass write.
1929
2055
  const result = reduce(values, { changed: this.#changedComputeField(event, inputs, scope) }) || {}
1930
2056
 
1931
- // Issue #183 SINGLE-PASS WRITE SET. Three ordered phases, so declared output
2057
+ // The reserved $ops output (issue #226) is CONSUMED here normalized once,
2058
+ // then excluded from every write phase below (it is an op chain, never a
2059
+ // field value, text sink, or mirror), and applied as phase 4.
2060
+ const reducerOps = computeOpsList(result.$ops)
2061
+
2062
+ // Issue #183 — SINGLE-PASS WRITE SET. Ordered phases, so declared output
1932
2063
  // order stops being semantics and a wrong order can no longer corrupt values:
1933
2064
  //
1934
2065
  // 1. BATCH the field writes from the ONE result. Each output name in the
@@ -1942,9 +2073,12 @@ export default class extends Controller {
1942
2073
  // per-instance WeakSet) makes recompute skip re-running the reducer for our
1943
2074
  // own write, while the event still fires every OTHER listener (chained
1944
2075
  // repaint, dirty tracking, show bindings, sibling roots).
2076
+ // 4. RUN the reducer's $ops chain (issue #226) — rising-edge, event-gated —
2077
+ // so its ops (dispatch a completion event, submit the form) always see
2078
+ // the fully settled DOM and every chained listener has already run.
1945
2079
  const changedFields = []
1946
2080
  for (const name of outputs) {
1947
- if (!(name in result)) continue
2081
+ if (name === "$ops" || !(name in result)) continue
1948
2082
  const field = ownedField(name)
1949
2083
  if (!field) continue // a non-field output paints as a text sink in phase 2
1950
2084
  if (String(result[name]) === field.value) continue // change-guard — unchanged, skip
@@ -1958,6 +2092,7 @@ export default class extends Controller {
1958
2092
  // undefined result value is SKIPPED (never stringified to "null"/"undefined") —
1959
2093
  // the same "no value this pass, don't paint" filter #applyComputeMirrors uses.
1960
2094
  for (const name of Object.keys(result)) {
2095
+ if (name === "$ops") continue
1961
2096
  const value = result[name]
1962
2097
  if (value === undefined || value === null) continue
1963
2098
  this.#mirrorText(name, value)
@@ -1979,6 +2114,29 @@ export default class extends Controller {
1979
2114
  this.#computeSelfDispatched.add(inputEvent)
1980
2115
  field.dispatchEvent(inputEvent)
1981
2116
  }
2117
+
2118
+ // Phase 4 — the reducer's $ops chain (issue #226), after everything settled.
2119
+ // Event-gated: a seed/direct recompute() pass (no event) arms the latch but
2120
+ // never fires, so a restored/re-rendered complete value can't auto-fire.
2121
+ this.#applyComputeOps(reducerOps, Boolean(event))
2122
+ }
2123
+
2124
+ // Apply a reducer-emitted $ops chain (issue #226) on its RISING EDGE, keyed
2125
+ // on CONTENT: fire only when THIS pass's chain differs from the LAST pass's
2126
+ // (including from "absent"), and only on an event-driven pass. An unchanged
2127
+ // chain never re-fires — "still complete, same submit" is settled, exactly
2128
+ // like the change-guarded field writes. A pass with no $ops re-arms; a pass
2129
+ // whose chain CHANGED fires again (per-keystroke focus advance across OTP
2130
+ // boxes is a different focus target each time — a deliberately new intent).
2131
+ // Ops run through the shared CLIENT_OPS interpreter with runOps's
2132
+ // root-scoped target resolution; a missing to: defaults to "@root"
2133
+ // (hand-written reducer ergonomics).
2134
+ #applyComputeOps(list, eventDriven) {
2135
+ const signature = list === null ? null : JSON.stringify(list)
2136
+ const fire = signature !== null && signature !== this.#computeOpsSignature && eventDriven
2137
+ this.#computeOpsSignature = signature
2138
+ if (!fire) return
2139
+ applyOps(list, (args) => this.#opTargets(args.to == null ? { ...args, to: "@root" } : args))
1982
2140
  }
1983
2141
 
1984
2142
  // Client-side list navigation (combobox keyboard nav, issue #72). Wired by
@@ -3388,6 +3546,60 @@ export default class extends Controller {
3388
3546
  return false
3389
3547
  }
3390
3548
 
3549
+ // The connect() gate for completion bindings (issue #226) — one attribute read.
3550
+ #onCompleteEnabled() {
3551
+ return !!this.element.getAttribute?.("data-reactive-on-complete")
3552
+ }
3553
+
3554
+ // Parse-and-memoize the completion bindings, keyed on the RAW attr string:
3555
+ // a morph that rewrote the payload re-parses and RESETS the latches (the
3556
+ // morph listener's own arm pass then re-arms without firing). A removed
3557
+ // attr yields [] silently; malformed JSON warns (in parseOnComplete).
3558
+ #onCompleteBindings() {
3559
+ const raw = this.element.getAttribute?.("data-reactive-on-complete") ?? null
3560
+ if (raw !== this.#onCompleteRaw) {
3561
+ this.#onCompleteRaw = raw
3562
+ this.#onCompleteParsed = raw == null ? [] : parseOnComplete(raw)
3563
+ this.#onCompleteStates = this.#onCompleteParsed.map(() => false)
3564
+ }
3565
+ return this.#onCompleteParsed
3566
+ }
3567
+
3568
+ // Evaluate every completion binding (issue #226) over the owned fields —
3569
+ // the SAME memoized, scope-aware field resolver and DNF fold the show sync
3570
+ // uses — and run each binding's ops on ITS OWN rising edge. `event` null
3571
+ // (the connect/morph arm pass) updates the latches WITHOUT firing, so ops
3572
+ // only ever run from a real user gesture. An undecidable payload (no
3573
+ // groups) leaves its latch alone — default-deny, like every malformed-wire
3574
+ // arm. Ops resolve through runOps's root-scoped targets; a missing to:
3575
+ // defaults to "@root" (the $ops convention).
3576
+ #syncOnComplete(event) {
3577
+ const bindings = this.#onCompleteBindings()
3578
+ if (!bindings.length) return
3579
+
3580
+ const owns = this.#ownershipFilter()
3581
+ const scope = this.element.getAttribute?.("data-reactive-scope") || null
3582
+ const values = new Map()
3583
+ const fieldValue = (name) => {
3584
+ if (!values.has(name)) values.set(name, this.#showFieldValue(name, owns, scope))
3585
+ return values.get(name)
3586
+ }
3587
+ bindings.forEach((binding, i) => {
3588
+ const matches = anyOfAllsMatches(binding.any, fieldValue)
3589
+ if (matches === null) return
3590
+ const fire = matches && !this.#onCompleteStates[i] && Boolean(event)
3591
+ this.#onCompleteStates[i] = matches
3592
+ if (fire) applyOps(binding.ops, (args) => this.#opTargets(args.to == null ? { ...args, to: "@root" } : args))
3593
+ })
3594
+ }
3595
+
3596
+ #teardownOnCompleteSync() {
3597
+ if (!this.#boundSyncOnComplete) return
3598
+ this.element.removeEventListener?.("input", this.#boundSyncOnComplete)
3599
+ this.element.removeEventListener?.("change", this.#boundSyncOnComplete)
3600
+ this.element.removeEventListener?.("turbo:morph-element", this.#boundArmOnComplete)
3601
+ }
3602
+
3391
3603
  // Re-evaluate every OWNED show binding in one pass (issue #161): read the
3392
3604
  // controlling field's current value, evaluate the declared literal predicate,
3393
3605
  // toggle `hidden`. A full pass (not per-target) for the same reason as
@@ -1,4 +1,4 @@
1
- import{Controller as WX}from"@hotwired/stimulus";import{confirmResolver as R}from"phlex/reactive/confirm";import{computeReducer as JX}from"phlex/reactive/compute";import{confirmPredicate as LX}from"phlex/reactive/confirm_predicate";function VX(){let X=window.Turbo?.StreamActions;if(!X||X["reactive:visit"])return;X["reactive:visit"]=function(){let Z=this.getAttribute("data-url");if(Z)window.Turbo.visit(Z,{action:"advance"})}}function BX(){let X=window.Turbo?.StreamActions;if(!X||X["reactive:token"])return;X["reactive:token"]=function(){let Z=this.getAttribute("data-reactive-token-value"),$=this.getAttribute("target");if(!Z||!$)return;let Q=document.getElementById($);if(Q)Q.setAttribute("data-reactive-token-value",Z)}}function _X(){let X=window.Turbo?.StreamActions;if(!X||X["reactive:js"])return;X["reactive:js"]=function(){let Z=HX(this.getAttribute("data-reactive-ops"));if(!Z.length)return;let $=this.getAttribute("target"),Q=$?document.getElementById($):null;if($&&!Q)return;UX(Z,(j)=>ZZ(j,Q))}}var _=new Map;function s(X,Z){let $=!_.has(X);if(_.set(X,Z),$)QX()}function N(X){if(_.delete(X))jX()}function GZ(){_.clear(),S=!1}function qZ(X){return _.get(X)?.via}var S=!1;function AX(){let X=window.Turbo?.StreamActions;if(!X||X["reactive:defer"])return;if(X["reactive:defer"]=function(){let Z=this.getAttribute("target");if(!Z)return;if(this.getAttribute("data-reactive-defer-via")==="stream"){OX(Z,this);return}let $=this.getAttribute("data-reactive-defer-token");if(!$)return;C(Z,$)},!S&&typeof document<"u"&&document.addEventListener)S=!0,document.addEventListener("turbo:before-stream-render",MX)}function MX(X){let $=X.target?.getAttribute?.("target");if(!$)return;let Q=$.startsWith("reactive-defer-src-")?$.slice(19):$;if(_.get(Q)?.via==="stream")N(Q)}function C(X,Z){let $=document.getElementById(X);if(!$){console.warn(`[phlex-reactive] reactive:defer target #${X} is not on the page — skipped`);return}a(X),r($);let Q={via:"fetch",abort:new AbortController,timedOut:!1};s(X,Q),xX(X,Q,Z)}function OX(X,Z){let $=document.getElementById(X);if(!$){console.warn(`[phlex-reactive] reactive:defer target #${X} is not on the page — skipped`);return}let Q=Z.getAttribute("data-reactive-defer-src");if(!Q)return;if(!globalThis.customElements?.get?.("pgbus-stream-source")){let z=Z.getAttribute("data-reactive-defer-token");if(z){C(X,z);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}a(X),r($);let j=document.createElement("pgbus-stream-source");j.id=n(X),j.setAttribute("src",Q),j.setAttribute("since-id",Z.getAttribute("data-reactive-defer-since-id")??"0"),j.setAttribute("hidden",""),document.body.appendChild(j),s(X,{via:"stream"})}function n(X){return`reactive-defer-src-${X}`}async function xX(X,Z,$){let Q=setTimeout(()=>{Z.timedOut=!0,Z.abort.abort()},CX()),j;try{j=await fetch(PX(),{method:"POST",headers:{Accept:"text/vnd.turbo-stream.html","Content-Type":"application/json","X-CSRF-Token":NX()},body:JSON.stringify({token:$}),credentials:"same-origin",signal:Z.abort.signal})}catch(G){if(clearTimeout(Q),_.get(X)!==Z)return;console.error("[phlex-reactive] deferred render failed",G),F(X,$);return}if(_.get(X)!==Z){clearTimeout(Q);return}if(j.status===204){clearTimeout(Q),g(X);return}if(!j.ok){clearTimeout(Q),console.error(`[phlex-reactive] deferred render failed: HTTP ${j.status}`),F(X,$,j.status);return}let z;try{z=await j.text()}catch(G){if(clearTimeout(Q),_.get(X)!==Z)return;console.error("[phlex-reactive] deferred render failed reading the body",G),F(X,$);return}if(clearTimeout(Q),_.get(X)!==Z)return;g(X),window.Turbo.renderStreamMessage(z)}function a(X){let Z=_.get(X);if(!Z)return;if(N(X),Z.via==="fetch")Z.abort.abort();else document.getElementById(n(X))?.remove?.()}function r(X){X.setAttribute("data-reactive-defer-pending","true"),X.setAttribute("aria-busy","true")}function t(X){X.removeAttribute("data-reactive-defer-pending"),X.removeAttribute("aria-busy")}function g(X){N(X);let Z=document.getElementById(X);if(!Z)return;t(Z),Z.removeAttribute("data-reactive-error")}function F(X,Z,$){N(X);let Q=document.getElementById(X);if(!Q)return;t(Q),Q.setAttribute("data-reactive-error","defer");let j=()=>{let z=document.getElementById(X);if(!z){console.warn("[phlex-reactive] defer retry() ignored — the target left the DOM");return}z.removeAttribute("data-reactive-error"),C(X,Z)};Q.dispatchEvent(new CustomEvent("reactive:error",{bubbles:!0,composed:!0,detail:{kind:"defer",target:X,status:$,retry:j}}))}function PX(){return document.querySelector('meta[name="phlex-reactive-defer-path"]')?.content||"/reactive/defer"}function NX(){return document.querySelector('meta[name="csrf-token"]')?.content??""}function CX(){let X=document.querySelector('meta[name="phlex-reactive-timeout"]')?.content,Z=Number(X);return Number.isFinite(Z)&&Z>0?Z:30000}var b=!1;function DX(){if(b)return;if(typeof document>"u"||!document.addEventListener)return;b=!0,document.addEventListener("turbo:before-stream-render",RX)}function RX(X){let Z=X.detail,$=Z?.render;if(typeof $!=="function"||$.__reactiveDismissWrapped){if(typeof requestAnimationFrame==="function")requestAnimationFrame(I);else setTimeout(I,0);return}let Q=async(j)=>{await $(j),I()};Q.__reactiveDismissWrapped=!0,Z.render=Q}function I(){let X=document.querySelectorAll("[data-reactive-dismiss-after]");for(let Z of X){if(Z.hasAttribute("data-reactive-dismiss-scheduled"))continue;let $=Number(Z.getAttribute("data-reactive-dismiss-after"));if(!Number.isFinite($)||$<=0)continue;Z.setAttribute("data-reactive-dismiss-scheduled",""),setTimeout(()=>Z.remove(),$)}}function YZ(){b=!1}var FX=Object.freeze({append:"enter",prepend:"enter",replace:"update",update:"update",remove:"exit"}),T=Object.freeze(["fade","slide","scale","highlight","shake"]),w="data-reactive-fx-pending",e=1000,h=!1;function IX(){if(h)return;if(typeof document>"u"||typeof document.addEventListener!=="function")return;h=!0,document.addEventListener("turbo:before-stream-render",TX)}function KZ(){h=!1}function TX(X){let Z=X.detail,$=Z?.render;if(typeof $!=="function"||$.__reactiveEffectsWrapped)return;let Q=Z?.newStream??X.target,j=FX[Q?.getAttribute?.("action")];if(!j||SX())return;let z=EX(Q,j);if(!z)return;let G=j==="exit"?async(q)=>{await hX(x(Q),z),await $(q)}:async(q)=>{let Y=j==="enter"?bX(Q):null;if(await $(q),j==="enter")wX(Y,z);else XX(x(Q),z)};G.__reactiveEffectsWrapped=!0,Z.render=G}function EX(X,Z){let $=X.getAttribute?.("data-reactive-effect");if($==="off")return null;if($)return c($,Z);let j=(Z==="enter"?kX(X):x(X))?.getAttribute?.(`data-reactive-effect-${Z}`);return j?c(j,Z):null}function x(X){let Z=X.getAttribute?.("target");return Z?document.getElementById?.(Z)??null:null}function kX(X){return X.querySelector?.("template")?.content?.firstElementChild??null}function c(X,Z){if(X.startsWith("[")){let Q=null;try{let j=JSON.parse(X);if(Array.isArray(j)&&j.length===3)Q=j.map(String)}catch{}if(Q)return{legs:Q};return console.warn(`[phlex-reactive] malformed effect legs ${JSON.stringify(X)} — skipped`),null}let $=X==="random"?T[Math.floor(Math.random()*T.length)]:X;if(!T.includes($))return console.warn(`[phlex-reactive] unknown effect ${JSON.stringify(X)} — skipped`),null;return{className:`reactive-fx--${$}-${Z}`}}function SX(){try{return typeof matchMedia==="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches}catch{return!1}}function bX(X){let Z=X.querySelector?.("template")?.content;if(!Z)return null;for(let $ of Array.from(Z.children??[]))$.setAttribute?.(w,"");return x(X)}function wX(X,Z){if(typeof X?.querySelectorAll!=="function")return;for(let $ of Array.from(X.querySelectorAll(`[${w}]`)))$.removeAttribute(w),XX($,Z)}async function hX(X,Z){if(!X?.classList)return;if(Z.legs){await ZX(X,Z.legs);return}X.classList.add(Z.className);let $=p(X);if($<=0){X.classList.remove(Z.className);return}await f(X,$),X.classList.remove(Z.className)}function XX(X,Z){if(!X?.classList)return;if(Z.legs){ZX(X,Z.legs);return}if(X.classList.contains(Z.className))X.classList.remove(Z.className),X.offsetWidth;X.classList.add(Z.className);let $=p(X);if($<=0){X.classList.remove(Z.className);return}let Q=X.__reactiveFxToken=(X.__reactiveFxToken??0)+1;f(X,$).then(()=>{if(X.__reactiveFxToken===Q)X.classList.remove(Z.className)})}async function ZX(X,Z){let[$,Q,j]=Z.map(yX),z=X.__reactiveFxToken=(X.__reactiveFxToken??0)+1;if(X.classList.remove(...$,...Q,...j),X.classList.add(...$,...Q),await vX(),X.__reactiveFxToken!==z)return;X.classList.remove(...Q),X.classList.add(...j);let G=p(X);if(G>0)await f(X,G);if(X.__reactiveFxToken!==z)return;X.classList.remove(...$,...j)}function yX(X){return String(X??"").split(/\s+/).filter(Boolean)}function p(X){if(typeof getComputedStyle!=="function")return 0;try{let Z=getComputedStyle(X),$=(z)=>String(z??"").split(",").reduce((G,q)=>Math.max(G,parseFloat(q)||0),0),Q=$(Z.animationDuration)+$(Z.animationDelay),j=$(Z.transitionDuration)+$(Z.transitionDelay);return Math.min(Math.max(Q,j)*1000,e)}catch{return 0}}function f(X,Z){return new Promise(($)=>{let Q=!1,j=()=>{if(Q)return;Q=!0,$()};X.addEventListener?.("animationend",j,{once:!0}),X.addEventListener?.("transitionend",j,{once:!0}),setTimeout(j,Math.min(Z+50,e))})}function vX(){return new Promise((X)=>{if(typeof requestAnimationFrame==="function")requestAnimationFrame(()=>X());else setTimeout(X,16)})}var y=!1;function pX(){if(y)return;if(typeof window>"u"||typeof document>"u")return;if(typeof window.addEventListener!=="function")return;y=!0;let X=()=>{let Z=document.documentElement;if(typeof Z?.toggleAttribute!=="function")return;Z.toggleAttribute("data-reactive-offline",globalThis.navigator?.onLine===!1)};X(),window.addEventListener("online",X),window.addEventListener("offline",X)}function HZ(){y=!1}var m="phlex-reactive:latency",P=!1;function fX(X){if(typeof sessionStorage>"u")return;sessionStorage.setItem(m,String(X))}function mX(){if(typeof sessionStorage>"u")return;sessionStorage.removeItem(m),P=!1}function uX(){if(typeof window>"u"||typeof document>"u")return;if(document.querySelector?.('meta[name="phlex-reactive-env"]')?.content!=="development")return;window.PhlexReactive={enableLatencySim:fX,disableLatencySim:mX}}function UZ(){P=!1}var $X="data-reactive-active",A=0;function QX(){if(A++,A===1)zX("reactive:busy")}function jX(){if(A===0)return;if(A--,A===0)zX("reactive:idle")}function WZ(){return A}function JZ(){A=0,(typeof document<"u"?document.documentElement:null)?.removeAttribute?.($X)}function zX(X){if(typeof document>"u")return;let Z=document.documentElement;if(typeof Z?.toggleAttribute==="function")Z.toggleAttribute($X,A>0);if(typeof document.dispatchEvent==="function"&&typeof CustomEvent==="function")document.dispatchEvent(new CustomEvent(X,{detail:{count:A}}))}function d(){VX(),BX(),_X(),AX(),DX(),IX(),pX(),uX()}function gX(X){return X.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}if(typeof window<"u")if(window.Turbo)d();else document.addEventListener("turbo:load",d,{once:!0});var D=!1;function cX(){if(D)return;if(typeof document>"u")return;let X=document.querySelectorAll('[data-controller~="reactive"]');if(!X||X.length===0)return;console.warn("[phlex-reactive] found "+X.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 LZ(){D=!1}function VZ(){D=!0}if(typeof window<"u"&&typeof document<"u"){let X=()=>setTimeout(cX,0);if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",X,{once:!0});else X()}var dX=new Set(["href","src","srcdoc","action","formaction","xlink:href","style"]);function lX(X){let Z=String(X).toLowerCase();return Z.startsWith("on")||dX.has(Z)}function iX(X,Z,$){let[Q,j,z]=Z;X.classList.add(Q,j),$(),requestAnimationFrame(()=>{X.classList.remove(j),X.classList.add(z)});let G=!1,q=()=>{if(G)return;G=!0,X.classList.remove(Q,z)};X.addEventListener("animationend",q,{once:!0}),setTimeout(q,350)}var l=Object.freeze({show:(X,Z)=>E(X,!1,Z),hide:(X,Z)=>E(X,!0,Z),toggle:(X,Z)=>E(X,!X.hidden,Z),add_class:(X,Z)=>X.classList.add(...Z.classes??[]),remove_class:(X,Z)=>X.classList.remove(...Z.classes??[]),toggle_class:(X,Z)=>(Z.classes??[]).forEach(($)=>X.classList.toggle($)),set_attr:(X,Z)=>{if(k(Z.name))X.setAttribute(Z.name,Z.value??"")},remove_attr:(X,Z)=>{if(k(Z.name))X.removeAttribute(Z.name)},toggle_attr:(X,Z)=>{if(!k(Z.name))return;if(X.hasAttribute(Z.name))X.removeAttribute(Z.name);else X.setAttribute(Z.name,"")},focus:(X)=>X.focus?.(),focus_first:(X)=>XZ(X)?.focus?.(),text:(X,Z)=>{let $=String(Z.value??"");if(X.textContent!==$)X.textContent=$},dispatch:(X,Z)=>{X.dispatchEvent(new CustomEvent(Z.name,{bubbles:!0,composed:!0,detail:Z.detail??{}}))}});function E(X,Z,$){if($?.transition)iX(X,$.transition,()=>X.hidden=Z);else X.hidden=Z}function k(X){if(!lX(X))return!0;return console.warn(`[phlex-reactive] refused client attr op on ${JSON.stringify(X)} — skipped`),!1}var GX=/^#[A-Za-z_][\w-]*$/;function oX(X){if(typeof X==="string"&&GX.test(X))return!0;return console.warn(`[phlex-reactive] refused cross-root mirror target ${JSON.stringify(X)} — skipped`),!1}function sX(X,Z){let $=X.getAttribute("data-reactive-show-equals");if($!==null)return Z===$;let Q=X.getAttribute("data-reactive-show-not");if(Q!==null)return Z!==Q;let j=X.getAttribute("data-reactive-show-in");if(j!==null){try{let z=JSON.parse(j);if(Array.isArray(z))return z.includes(Z)}catch{}return console.warn(`[phlex-reactive] malformed reactive_show in: list ${JSON.stringify(j)} — skipped`),null}for(let z of qX){let G=X.getAttribute(`data-reactive-show-${z}`);if(G!==null)return YX(z,G,Z)}return console.warn("[phlex-reactive] a reactive_show binding declares no predicate — skipped"),null}var qX=["gte","gt","lte","lt"];function YX(X,Z,$){let Q=Number(Z);if(Number.isNaN(Q))return console.warn(`[phlex-reactive] reactive_show ${X}: needs a numeric literal, got ${JSON.stringify(Z)} — skipped`),null;let j=$==null?"":String($).trim(),z=j===""?NaN:Number(j);if(Number.isNaN(z))return!1;switch(X){case"gte":return z>=Q;case"gt":return z>Q;case"lte":return z<=Q;case"lt":return z<Q;default:return null}}function KX(X,Z){if(!X||typeof X!=="object")return null;if(typeof X.equals==="string")return Z===X.equals;if(typeof X.not==="string")return Z!==X.not;if(Array.isArray(X.in))return X.in.includes(Z);for(let $ of qX)if($ in X)return YX($,X[$],Z);return null}var i="[data-reactive-show-field], [data-reactive-show]";function nX(X){try{let Z=JSON.parse(X);if(Z&&typeof Z==="object"&&!Array.isArray(Z))return Z}catch{}return console.warn(`[phlex-reactive] malformed compound reactive_show payload ${JSON.stringify(X)} — skipped`),null}function u(X,Z){if(!X||typeof X!=="object"||typeof X.field!=="string")return!1;let $=Z(X.field)??"";return KX(X,$)===!0}function v(X,Z){if(!Array.isArray(X)||X.length===0)return null;return X.some(($)=>Array.isArray($)&&$.length>0&&$.every((Q)=>u(Q,Z)))}function aX(X){if(!Array.isArray(X)||X.length===0)return null;let Z=new Set;for(let $ of X){if(!Array.isArray($))continue;for(let Q of $)if(Q&&typeof Q==="object"&&typeof Q.field==="string")Z.add(Q.field)}return Z.size>0?[...Z]:null}function rX(X,Z){if(!X||typeof X!=="object")return null;let $=X.any;if(Array.isArray($)&&($.length===0||Array.isArray($[0])))return v($,Z);return tX(X,Z)}function tX(X,Z){let $=Array.isArray(X.all)?"all":Array.isArray(X.any)?"any":null;if(!$)return null;let Q=X[$];if(Q.length===0)return null;let j=Q.map((z)=>u(z,Z));return $==="all"?j.every(Boolean):j.some(Boolean)}function o(X){if(typeof X==="string"&&GX.test(X))return!0;return console.warn(`[phlex-reactive] refused cross-root show target ${JSON.stringify(X)} — skipped`),!1}var eX='a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';function XZ(X){return X.querySelectorAll?.(eX)?.[0]??null}function HX(X){if(Array.isArray(X))return X;if(typeof X!=="string")return[];try{let Z=JSON.parse(X);return Array.isArray(Z)?Z:[]}catch{return[]}}function UX(X,Z){for(let $ of X){if(!Array.isArray($))continue;let[Q,j={}]=$;if(!Object.hasOwn(l,Q)){console.warn(`[phlex-reactive] unknown client op ${JSON.stringify(Q)} — skipped`);continue}for(let z of Z(j))l[Q](z,j)}}function ZZ(X,Z){let $=X.to;if(Z){if($==="@root")return[Z];if(typeof $!=="string"||$==="")return[];if(X.global)return[...document.querySelectorAll($)];return[...Z.querySelectorAll($)]}if(typeof $!=="string"||$===""||$==="@root")return[];return[...document.querySelectorAll($)]}class BZ extends WX{static values={token:String};#m;#M=new Map;#H=new Map;#_X;#I;#u;#T=0;#U=new Map;#E=new WeakMap;#O=new Map;#g=new WeakSet;#W;#J;#L;#Z;#Q;#V;#c=!1;#k=0;#d=!1;#j;#B;#_;#x;connect(){if(D=!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.#l(),this.#x=()=>this.#l(),this.element.addEventListener?.("turbo:morph-element",this.#x);if(this.#AX()){if(this.#W=()=>this.#h(),this.element.addEventListener?.("turbo:morph-element",this.#W),this.#h(),this.element.getAttribute?.("data-reactive-warn-unsaved")==="true")this.#cX()}if(this.#lX())this.#Z=()=>this.#GX(),this.element.addEventListener?.("input",this.#Z),this.element.addEventListener?.("change",this.#Z),this.element.addEventListener?.("turbo:morph-element",this.#Z),this.#GX();if(this.#R())this.#Q=(X)=>{if(X?.type==="input"&&!this.#tX(X))return;this.#A()},this.element.addEventListener?.("input",this.#Q),this.element.addEventListener?.("turbo:morph-element",this.#Q),this.#A();if(this.#F())this.#V=()=>this.#p(),this.element.addEventListener?.("turbo:morph-element",this.#V),this.#p();if(this.#XZ())this.#j=(X)=>this.syncNestedJson(X),this.#B=()=>this.#N(),this.element.addEventListener?.("input",this.#j),this.element.addEventListener?.("change",this.#j),this.element.addEventListener?.("turbo:morph-element",this.#B),this.#N();if(this.#rX())this.#_=()=>this.recompute(),this.element.addEventListener?.("turbo:morph-element",this.#_),this.recompute()}#AX(){if((this.element.getAttribute?.("data-action")??"").includes("reactive#trackDirty"))return!0;let X=this.element.querySelectorAll?.('[data-action*="reactive#trackDirty"]')??[];for(let Z of X)if(this.#$(Z))return!0;return!1}disconnect(){if(this.#SX(),this.#wX(),this.#dX(),this.#aX(),this.#eX(),this.#qZ(),this.#YZ(),this.#KZ(),this.#x)this.element.removeEventListener?.("turbo:morph-element",this.#x)}#l(){let X=this.element;if(!X?.id)return;let Z=X.getAttribute?.("data-reactive-defer-token");if(!Z)return;if(X.getAttribute?.("data-reactive-defer-pending")!=="true")return;C(X.id,Z)}dispatch(X){let{action:Z,params:$,debounce:Q,throttle:j,confirm:z,confirmWhen:G,outside:q,window:Y,optimistic:K}=X.params;if(!Z)return;let W=X.params.busy??this.#MZ(X.params.loading);if(q&&this.element.contains(X.target))return;let B=X.currentTarget??X.target;if(!Y&&!this.#WZ(K,B))X.preventDefault();let J=this.#w(z,G);if(!J)return this.#e(B,Z,$,Q,j,K,W);Promise.resolve().then(()=>R(J,{el:B})).catch(()=>!1).then((L)=>{if(L)this.#e(B,Z,$,Q,j,K,W)})}runOps(X){let{ops:Z,confirm:$,confirmWhen:Q,outside:j,window:z}=X.params,G=X.currentTarget??X.target;if(j&&this.element.contains(X.target))return;if(!z)X.preventDefault();let q=this.#w($,Q);if(!q)return this.#WX(this.#UX(Z));Promise.resolve().then(()=>R(q,{el:G})).catch(()=>!1).then((Y)=>{if(Y)this.#WX(this.#UX(Z))})}trackDirty(){this.#h()}recompute(X){if(X&&this.#g.has(X))return;let Z=this.#DX(),$=Z.map(([H])=>H),Q=this.element.getAttribute?.("data-reactive-scope")||null,j=(H)=>Q&&!H.includes("[")?`${Q}[${H}]`:H,z=this.#X(),G=new Map,q=(H)=>{if(G.has(H))return G.get(H);let U=null;for(let M of this.element.querySelectorAll(`[name="${j(H)}"]`))if(z(M)){U=M;break}return G.set(H,U),U};for(let H of $)this.#r(H,q(H)?.value??"");let Y=this.element.getAttribute("data-reactive-compute-reducer-param"),K=Y?JX(Y):null;if(!K){this.#t({},q);return}let W=this.#CX("data-reactive-compute-outputs-param"),B={};for(let[H,U]of Z){let M=q(H);if(U==="string")B[H]=M?.value??"";else{let V=Number(M?.value);B[H]=Number.isFinite(V)?V:0}}let J=K(B,{changed:this.#RX(X,$,Q)})||{},L=[];for(let H of W){if(!(H in J))continue;let U=q(H);if(!U)continue;if(String(J[H])===U.value)continue;U.value=J[H],L.push(U)}for(let H of Object.keys(J)){let U=J[H];if(U===void 0||U===null)continue;this.#r(H,U)}this.#t(J,q);for(let H of L){let U=new Event("input",{bubbles:!0});this.#g.add(U),H.dispatchEvent(U)}}listnavNext(X){this.#i(X,1)}listnavPrev(X){this.#i(X,-1)}listnavPick(X){let Z=this.#P(X),$=Z.findIndex((Q)=>Q.hasAttribute("data-reactive-highlighted"));if($<0)return;X.preventDefault(),Z[$].click()}listnavClose(X){for(let Z of this.#P(X))Z.removeAttribute("data-reactive-highlighted")}#i(X,Z){let $=this.#P(X);if(!$.length)return;X.preventDefault();let Q=$.findIndex((G)=>G.hasAttribute("data-reactive-highlighted")),j=Q<0?Z>0?0:$.length-1:(Q+Z+$.length)%$.length;for(let G of $)G.removeAttribute("data-reactive-highlighted");let z=$[j];z.setAttribute("data-reactive-highlighted","true"),z.scrollIntoView?.({block:"nearest"})}#P(X){let $=(X?.currentTarget??X?.target??this.element).getAttribute?.("data-reactive-listnav-option-param")??this.element.getAttribute("data-reactive-listnav-option-param");if(!$)return[];let Q=this.#X();return Array.from(this.element.querySelectorAll($)).filter((j)=>!j.hidden&&Q(j))}tagsAdd(X){if(!this.#F())return;if(X?.defaultPrevented)return;if(this.#P(X).some((Q)=>Q.hasAttribute?.("data-reactive-highlighted")))return;X?.preventDefault?.();let Z=X?.currentTarget??X?.target;if(!Z)return;if(!this.#YX(String(Z.value??"").split(",")))return;if(Z.value="",this.#R())this.#A()}tagsPick(X){if(!this.#F())return;X?.preventDefault?.();let $=(X?.currentTarget??X?.target)?.getAttribute?.("data-reactive-tag-param");if(!$)return;if(!this.#YX([$]))return;let Q=this.#ZZ();if(!Q)return;Q.value="",this.#A(),Q.focus?.()}tagsRemove(X){if(!this.#F())return;X?.preventDefault?.();let $=(X?.currentTarget??X?.target)?.getAttribute?.("data-reactive-tag-param");if(!$)return;let Q=this.#y();if(!Q)return;let j=this.#v(Q),z=j.filter((G)=>G.toLowerCase()!==$.toLowerCase());if(z.length===j.length)return;this.#KX(Q,z)}nestedAdd(X){X?.preventDefault?.();let Z=X?.currentTarget??X?.target,$=Z?.getAttribute?.("data-reactive-association-param");if(!$)return;if(typeof this.element?.querySelectorAll!=="function")return;let Q=this.#X(),j=[...this.element.querySelectorAll(`[data-reactive-nested-list="${$}"]`)].find(Q),G=[...this.element.querySelectorAll(`[data-reactive-nested-template="${$}"]`)].find(Q)?.content?.firstElementChild;if(!j||!G){this.#GZ($);return}let q=G.cloneNode(!0);this.#zZ(q,this.#jZ()),j.appendChild(q);let Y=Z?.getAttribute?.("data-reactive-nested-from-param"),K=Z?.getAttribute?.("data-reactive-nested-clear-param")==="true",W=this.#MX(q,Y,K);if(Y)W?.focus?.();else[...q.querySelectorAll?.("input, select, textarea")??[]][0]?.focus?.();if(j.getAttribute?.("data-reactive-nested-json")===$)this.#s($)}#MX(X,Z,$){if(!Z)return null;let Q;try{Q=JSON.parse(Z)}catch{return null}if(!Q||typeof Q!=="object")return null;let j=this.#X(),z=[...X.querySelectorAll?.("input, select, textarea")??[]],G=[];for(let[q,Y]of Object.entries(Q)){let K=[...this.element.querySelectorAll?.(Y)??[]].find(j);if(!K)continue;let W=z.find((B)=>this.#a(B.getAttribute?.("name"))===q);if(!W)continue;this.#OX(W,K),G.push(K)}if($)for(let q of G)this.#xX(q);return G[0]??null}#OX(X,Z){if(X.type==="checkbox")X.checked=Z.type==="checkbox"?!!Z.checked:this.#S(Z)!=="";else X.value=this.#S(Z);if(typeof X.dispatchEvent==="function")X.dispatchEvent(new Event("input",{bubbles:!0}))}#xX(X){if(X.type==="checkbox")X.checked=!1;else X.value="";if(typeof X.dispatchEvent==="function")X.dispatchEvent(new Event("input",{bubbles:!0}))}nestedRemove(X){X?.preventDefault?.();let Z=X?.currentTarget??X?.target,$=Z?.closest?.("[data-reactive-nested-row]");if(!$)return;if($.closest?.('[data-controller~="reactive"]')!==this.element)return;let Q=Z?.getAttribute?.("data-reactive-confirm-param"),j=Z?.getAttribute?.("data-reactive-confirm-when-param"),z=this.#w(Q,j);if(!z)return this.#o($);let G=this.#n($),q=this.#PX(z,G);return Promise.resolve().then(()=>R(q,{el:Z,row:$,fields:G})).catch(()=>!1).then((Y)=>{if(Y)this.#o($)})}#PX(X,Z){if(!X.includes("%{"))return X;return X.replace(/%\{(\w+)\}/g,($,Q)=>Object.prototype.hasOwnProperty.call(Z,Q)?Z[Q]:$)}#o(X){let Z=[...X.querySelectorAll?.('input[name$="[_destroy]"]')??[]][0];if(Z){if(Z.value="1",typeof Z.dispatchEvent==="function")Z.dispatchEvent(new Event("input",{bubbles:!0}));X.hidden=!0}else X.parentNode?.removeChild?.(X);this.#N()}syncNestedJson(X){let Z=X?.target;if(!Z||!this.#$(Z))return;this.#N()}#N(){if(typeof this.element?.querySelectorAll!=="function")return;let X=this.#X();for(let Z of[...this.element.querySelectorAll("[data-reactive-nested-json]")].filter(X))this.#s(Z.getAttribute("data-reactive-nested-json"))}#s(X){let Z=this.#X(),$=[...this.element.querySelectorAll(`[data-reactive-nested-list="${X}"]`)].find(Z);if(!$)return;let Q=this.#NX($);if(!Q)return;let j=[];for(let G of[...$.querySelectorAll?.("[data-reactive-nested-row]")??[]]){if(!Z(G)||G.hidden)continue;j.push(this.#n(G))}let z=JSON.stringify(j);if(Q.value===z)return;if(Q.value=z,typeof Q.dispatchEvent==="function")Q.dispatchEvent(new Event("input",{bubbles:!0}))}#NX(X){let Z=X.getAttribute?.("data-reactive-nested-json-field");if(!Z)return null;let $=this.#X();return[...this.element.querySelectorAll(Z)].find($)??null}#n(X){let Z={};for(let $ of[...X.querySelectorAll?.("input, select, textarea")??[]]){let Q=this.#a($.getAttribute?.("name"));if(Q===null||Q==="_destroy")continue;Z[Q]=this.#S($)}return Z}#a(X){if(!X)return null;let Z=X.match(/\[([^\][]+)\]$/);return Z?Z[1]:X}#S(X){if(X.type==="checkbox")return X.checked?X.value||"on":"";return X.value??""}#CX(X){let Z=this.element.getAttribute(X);if(!Z)return[];try{let $=JSON.parse(Z);return Array.isArray($)?$:[]}catch{return[]}}#DX(){let X=this.element.getAttribute("data-reactive-compute-inputs-param");if(!X)return[];try{let Z=JSON.parse(X);if(Array.isArray(Z))return Z.map(($)=>[$,"number"]);if(Z&&typeof Z==="object")return Object.entries(Z);return[]}catch{return[]}}#RX(X,Z,$){let Q=X?.target;if(!Q?.name||typeof Q.closest!=="function")return null;let j=this.#FX(Q.name,$);if(!Z.includes(j))return null;return this.#$(Q)?j:null}#FX(X,Z){if(!Z)return X;let $=`${Z}[`;return X.startsWith($)&&X.endsWith("]")?X.slice($.length,-1):X}#r(X,Z){let $=String(Z);for(let Q of this.#IX(X)){if(Q.textContent===$)continue;Q.textContent=$}}#IX(X){let Z=this.element.querySelectorAll(`[data-reactive-text="${X}"]`);return Array.from(Z).filter(($)=>this.#$($))}#t(X,Z){let $=this.#TX();for(let[Q,j]of Object.entries($)){let z=Q in X?X[Q]:Z(Q)?.value;if(z===void 0||z===null)continue;let G=String(z);for(let q of Array.isArray(j)?j:[j]){if(!oX(q))continue;for(let Y of document.querySelectorAll(q)){if(Y.textContent===G)continue;Y.textContent=G}}}}#TX(){let X=this.element.getAttribute("data-reactive-compute-mirror-param");if(!X)return{};try{let Z=JSON.parse(X);return Z&&typeof Z==="object"&&!Array.isArray(Z)?Z:{}}catch{return{}}}#e(X,Z,$,Q,j,z,G){if(this.#D("reactive:before-dispatch",{action:Z,params:this.#HX($),element:this.element},{cancelable:!0}).defaultPrevented)return;let Y=Number(Q)||0;if(Y>0)return this.#kX(X,Y,Z,$,z,G);let K=Number(j)||0;if(K>0)return this.#bX(X,K,Z,$,z,G);return this.#C(Z,$,z,X,G)}#C(X,Z,$,Q,j){let z=this.#JZ($,Q),G=this.#LZ(X,Q,j),q=this.#XX()?this.#EX($,Q):null;return this.queue=(this.queue??Promise.resolve()).then(()=>this.#mX(X,Z,z,G,q)),this.queue}#EX(X,Z){if(!X?.hide)return null;let $=this.#VX(X,Z);if(!$.length)return null;return()=>{let Q=$.filter((j)=>j.isConnected&&!j.hidden);if(!Q.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.",Q)}}#kX(X,Z,$,Q,j,z){this.#b(X);let G=()=>{this.#b(X),this.#C($,Q,j,X,z)},q=setTimeout(G,Z);X?.addEventListener?.("blur",G,{once:!0}),this.#M.set(X,{timer:q,flush:G})}#b(X){let Z=this.#M.get(X);if(!Z)return;clearTimeout(Z.timer),X?.removeEventListener?.("blur",Z.flush),this.#M.delete(X)}#SX(){for(let X of[...this.#M.keys()])this.#b(X)}#bX(X,Z,$,Q,j,z){let G=this.#H.get(X)??new Map;if(G.has($))return;let q=setTimeout(()=>{if(G.delete($),G.size===0)this.#H.delete(X)},Z);return G.set($,q),this.#H.set(X,G),this.#C($,Q,j,X,z)}#wX(){for(let X of this.#H.values())for(let Z of X.values())clearTimeout(Z);this.#H.clear()}#D(X,Z,{cancelable:$=!1}={}){let Q=new CustomEvent(X,{bubbles:!0,composed:!0,cancelable:$,detail:Z});return(this.element.isConnected?this.element:document).dispatchEvent(Q),Q}#z(X,Z,$,Q){let j=()=>{if(!this.element.isConnected){console.warn("[phlex-reactive] retry() ignored — the reactive root left the DOM");return}return this.#C(X,Z)};this.#D("reactive:error",{action:X,params:$,...Q,retry:j})}#G(X){if(this.element?.isConnected===!1)return;this.element?.setAttribute?.("data-reactive-error",X)}#hX(){this.element?.removeAttribute?.("data-reactive-error")}#yX(){let X=document.querySelector("[data-reactive-error-flash]");if(!X?.content)return;let Z=X.getAttribute("data-reactive-error-flash")||"flash",$=document.getElementById(Z);if(!$)return;$.appendChild(X.content.cloneNode(!0))}#vX(){if(typeof sessionStorage>"u")return Promise.resolve();let X=Number(sessionStorage.getItem(m));if(!Number.isFinite(X)||X<=0)return Promise.resolve();if(!P)P=!0,console.warn(`[phlex-reactive] latency simulator ACTIVE — every action is delayed by ${X}ms. Call PhlexReactive.disableLatencySim() (or clear sessionStorage) to turn it off.`);return new Promise((Z)=>setTimeout(Z,X))}#XX(){return this.element?.getAttribute?.("data-reactive-debug")==="true"}#ZX(){return typeof performance<"u"&&typeof performance.now==="function"?performance.now():Date.now()}#pX(X){if(!X)return[];let Z=[],$=/<turbo-stream\b([^>]*)>/g,Q;while((Q=$.exec(X))!==null){let j=Q[1],z=j.match(/\baction="([^"]*)"/)?.[1]??"?",G=j.match(/\btarget="([^"]*)"/)?.[1];Z.push(G?`${z} → #${G}`:z)}return Z}#fX(X){let{action:Z,status:$,ms:Q}=X,z=`reactive ${this.element?.id?`#${this.element.id} `:""}${Z} → ${$??"—"} (${Math.round(Q)}ms)`;if(console.groupCollapsed(z),console.log(`params: [${X.paramNames.join(", ")}] + collected: [${X.fieldNames.join(", ")}]`),console.log(`encoding: ${X.encoding}`),X.streams.length)console.log(`streams: ${X.streams.join(" ")}`);console.log(`token: ${X.tokenRefreshed?"refreshed ✓":"unchanged"}`),console.groupEnd()}async#mX(X,Z,$,Q,j){let{fields:z,files:G}=this.#jX(),q=this.#HX(Z),Y={...z,...q},K=this.#q,W=G.length>0,B=W?this.#HZ(K,X,Y,G):JSON.stringify({token:K,act:X,params:Y}),J=this.#XX()?{action:X,paramNames:Object.keys(q),fieldNames:Object.keys(z),encoding:W?"multipart":"json",status:null,streams:[],tokenRefreshed:!1,started:this.#ZX()}:null;await this.#vX();try{if(navigator.onLine===!1){this.#Y($),this.#G("offline"),this.#z(X,Z,Y,{kind:"offline"});return}let L;try{let V={Accept:"text/vnd.turbo-stream.html","X-CSRF-Token":this.#PZ()};if(!W)V["Content-Type"]="application/json";let O=this.#NZ();if(O)V["X-Pgbus-Connection"]=O;L=await fetch(this.#OZ(),{method:"POST",headers:V,body:B,credentials:"same-origin",signal:AbortSignal.timeout(this.#xZ())})}catch(V){if(console.error("[phlex-reactive] action error",V),this.#Y($),V?.name==="TimeoutError"||V?.name==="AbortError"){this.#G("timeout"),this.#z(X,Z,Y,{kind:"timeout"});return}this.#yX(),this.#G("network"),this.#z(X,Z,Y,{kind:"network"});return}if(J)J.status=L.status;if(L.redirected){console.error("[phlex-reactive] action was redirected (auth/CSRF?) — no update applied"),this.#Y($),this.#G("redirected"),this.#z(X,Z,Y,{kind:"redirected",status:L.status});return}if(!L.ok){let V=await L.text();if(console.error(`[phlex-reactive] action failed: HTTP ${L.status}`,V),this.#Y($),(L.headers.get("Content-Type")||"").includes("turbo-stream")){let O=this.#QX(V);if(this.#q=O??this.#q,J)this.#$X(J,V,O);window.Turbo.renderStreamMessage(V)}this.#G("http"),this.#z(X,Z,Y,{kind:"http",status:L.status,body:V});return}let H=L.headers.get("Content-Type")||"";if(!H.includes("turbo-stream")){console.error(`[phlex-reactive] expected a turbo-stream, got "${H}" — no update applied`),this.#Y($),this.#G("content-type"),this.#z(X,Z,Y,{kind:"content-type",status:L.status});return}let U=await L.text(),M=this.#QX(U);if(this.#q=M??this.#q,J)this.#$X(J,U,M);if(window.Turbo.renderStreamMessage(U),j)queueMicrotask(j);this.#hX(),this.#D("reactive:applied",{action:X,params:Y,html:U})}catch(L){console.error("[phlex-reactive] action error",L),this.#Y($),this.#D("reactive:error",{action:X,params:Y,kind:"apply"})}finally{if(Q?.(),J)this.#fX({...J,ms:this.#ZX()-J.started})}}#$X(X,Z,$){X.streams=this.#pX(Z),X.tokenRefreshed=$!=null}get#q(){return this.#m??this.tokenValue}set#q(X){this.#m=X}#QX(X){let Z=this.element.id;if(!Z)return X.match(/data-reactive-token-value="([^"]+)"/)?.[1];let{token:$,self:Q}=this.#uX(Z),j=X.match($);if(j)return j[1];let z=X.match(Q);if(z)return z[1].match(/data-reactive-token-value="([^"]+)"/)?.[1];return}#uX(X){let Z=this.#u;if(Z&&Z.id===X)return Z;let $=gX(X);return this.#u={id:X,token:new RegExp(`<turbo-stream\\b[^>]*\\baction="reactive:token"[^>]*\\btarget="${$}"[^>]*\\bdata-reactive-token-value="([^"]+)"`),self:new RegExp(`<turbo-stream\\b[^>]*\\baction="(?:replace|update)"[^>]*\\btarget="${$}"[^>]*>([\\s\\S]*?)</turbo-stream>`)}}#$(X){return X.closest('[data-controller~="reactive"]')===this.element}#X(){if(this.element.querySelectorAll('[data-controller~="reactive"]').length===0)return()=>!0;return(Z)=>this.#$(Z)}#w(X,Z){if(X)return X;if(!Z)return null;let $=Z;if(typeof Z==="string")try{$=JSON.parse(Z)}catch{return console.warn(`[phlex-reactive] malformed conditional confirm payload ${JSON.stringify(Z)} — skipped`),null}if(!$||typeof $!=="object")return null;let{fields:Q}=this.#jX(),j=(G)=>Q[G],z;if(typeof $.predicate==="string"){let G=LX($.predicate);if(!G)return console.warn(`[phlex-reactive] confirm predicate "${$.predicate}" is not registered — proceeding without a dialog (register it with setConfirmPredicate)`),null;z=!!G(Q)}else z=v($.groups?.any,j)===!0;return z?$.message:null}#jX(){let X={},Z=[],$=this.#X();return this.element.querySelectorAll("input[name], select[name], textarea[name]").forEach((Q)=>{if(!$(Q))return;if(Q.type==="file")for(let j of Q.files??[])Z.push({name:Q.name,file:j,multiple:Q.multiple});else if(Q.type==="checkbox")X[Q.name]=Q.checked;else if(Q.type==="radio"){if(Q.checked)X[Q.name]=Q.value}else X[Q.name]=Q.value}),this.element.querySelectorAll("[name]:is(lexxy-editor, trix-editor, [contenteditable=''], [contenteditable=true], [contenteditable=plaintext-only])").forEach((Q)=>{if(!$(Q))return;let j=Q.getAttribute("name");if(!j)return;let z=X[j];if(z==null||z==="")X[j]=Q.value??Q.textContent??Q.innerHTML??""}),{fields:X,files:Z}}#h(){if(typeof this.element?.querySelectorAll!=="function")return;let X=0;if(this.element.querySelectorAll("input[name], select[name], textarea[name]").forEach((Z)=>{if(!this.#$(Z))return;if(Z.type==="file")return;if(this.#gX(Z))Z.setAttribute("data-reactive-dirty","true"),X++;else Z.removeAttribute("data-reactive-dirty")}),X>0)this.element.setAttribute("data-reactive-dirty",String(X));else this.element.removeAttribute("data-reactive-dirty")}#gX(X){if(X.type==="checkbox"||X.type==="radio")return X.checked!==X.defaultChecked;if(X.tag==="select"||X.options)return Array.from(X.options??[]).some((Z)=>Z.selected!==Z.defaultSelected);return X.value!==X.defaultValue}#zX(){let X=this.element.getAttribute?.("data-reactive-dirty"),Z=Number(X);return Number.isFinite(Z)&&Z>0?Z:0}#cX(){if(typeof window>"u"||typeof window.addEventListener!=="function")return;this.#J=(X)=>{if(this.#zX()===0)return;return X.preventDefault(),X.returnValue="You have unsaved changes.",X.returnValue},this.#L=(X)=>{if(this.#zX()===0)return;if(!(typeof window.confirm==="function"?window.confirm("You have unsaved changes. Leave anyway?"):!0))X.preventDefault?.()},window.addEventListener("beforeunload",this.#J),window.addEventListener("turbo:before-visit",this.#L)}#dX(){if(this.#W)this.element.removeEventListener?.("turbo:morph-element",this.#W),this.#W=void 0;if(typeof window<"u"&&typeof window.removeEventListener==="function"){if(this.#J)window.removeEventListener("beforeunload",this.#J);if(this.#L)window.removeEventListener("turbo:before-visit",this.#L)}this.#J=void 0,this.#L=void 0}#lX(){if(this.element.getAttribute?.("data-reactive-show-targets"))return!0;let X=this.element.querySelectorAll?.(i)??[];for(let Z of X)if(this.#$(Z))return!0;return!1}#GX(){if(typeof this.element?.querySelectorAll!=="function")return;let X=this.#X(),Z=this.element.getAttribute?.("data-reactive-scope")||null,$=new Map,Q=(j)=>{if(!$.has(j))$.set(j,this.#nX(j,X,Z));return $.get(j)};for(let j of this.element.querySelectorAll(i)){if(!X(j))continue;let z=j.getAttribute("data-reactive-show");if(z!==null){let K=rX(nX(z),Q);if(K!==null)this.#qX(j,K,X,Z);continue}let G=j.getAttribute("data-reactive-show-field");if(!G)continue;let q=Q(G);if(q===null)continue;let Y=sX(j,q);if(Y===null)continue;this.#qX(j,Y,X,Z)}this.#iX(Q)}#qX(X,Z,$,Q){if(X.hidden=!Z,X.getAttribute("data-reactive-show-disable")!=="true")return;if(typeof X.querySelectorAll!=="function")return;for(let j of X.querySelectorAll("input[name], select[name], textarea[name]"))if($(j))j.disabled=!Z;if(X.name&&$(X))X.disabled=!Z}#iX(X){let Z=this.#sX();for(let[$,Q]of Object.entries(Z)){if($.startsWith("#")){this.#oX($,Q,X);continue}if(!Q||typeof Q!=="object"||Array.isArray(Q))continue;let j=X($);if(j===null)continue;let z=()=>j;for(let[G,q]of Object.entries(Q)){if(!o(G))continue;let Y;if(Array.isArray(q)){if(q.length===0){console.warn(`[phlex-reactive] malformed reactive_show_targets group for ${G} — skipped`);continue}Y=q.every((K)=>u(K,z))}else{let K=KX(q,j);if(K===null){console.warn(`[phlex-reactive] malformed reactive_show_targets predicate for ${G} — skipped`);continue}Y=K}for(let K of document.querySelectorAll(G))K.hidden=!Y}}}#oX(X,Z,$){if(!o(X))return;let Q=Z&&typeof Z==="object"&&!Array.isArray(Z)?Z.any:null,j=aX(Q);if(j===null){console.warn(`[phlex-reactive] malformed reactive_show_targets conditions for ${X} — skipped`);return}if(j.every((G)=>$(G)===null))return;let z=v(Q,$);if(z===null)return;for(let G of document.querySelectorAll(X))G.hidden=!z}#sX(){let X=this.element.getAttribute?.("data-reactive-show-targets");if(!X)return{};try{let Z=JSON.parse(X);if(Z&&typeof Z==="object"&&!Array.isArray(Z))return Z}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: { ... })"),{}}#nX(X,Z,$){let Q=$&&!X.includes("[")?`${$}[${X}]`:X,j=!1,z=null;for(let G of this.element.querySelectorAll(`[name="${Q}"]`)){if(!Z(G))continue;if(G.type==="checkbox")return G.checked?"true":"false";if(G.type==="radio"){if(G.checked)return G.value??"";j=!0;continue}z??=G}if(z)return z.value??"";return j?"":null}#aX(){if(!this.#Z)return;this.element.removeEventListener?.("input",this.#Z),this.element.removeEventListener?.("change",this.#Z),this.element.removeEventListener?.("turbo:morph-element",this.#Z),this.#Z=void 0}#R(){return!!(this.element.getAttribute?.("data-reactive-filter-input")&&this.element.getAttribute?.("data-reactive-filter-option"))}#rX(){return this.element.getAttribute?.("data-reactive-compute-seed")==="true"}#tX(X){let Z=this.element.getAttribute("data-reactive-filter-input");return!!Z&&typeof X.target?.matches==="function"&&X.target.matches(Z)}#A(){if(typeof this.element?.querySelectorAll!=="function")return;let X=this.element.getAttribute("data-reactive-filter-input"),Z=this.element.getAttribute("data-reactive-filter-option");if(!X||!Z)return;let $=this.#X(),Q=[...this.element.querySelectorAll(X)].find($);if(!Q)return;let j=(Q.value??"").trim().toLowerCase(),z=0;for(let Y of this.element.querySelectorAll(Z)){if(!$(Y))continue;let K=(Y.getAttribute("data-reactive-filter-text")??Y.textContent??"").toLowerCase(),W=Y.hasAttribute?.("data-reactive-tags-selected")||j!==""&&!K.includes(j);if(Y.hidden=W,W)Y.removeAttribute("data-reactive-highlighted");else z++}let G=this.element.getAttribute("data-reactive-filter-group");if(G)for(let Y of this.element.querySelectorAll(G)){if(!$(Y))continue;let K=[...Y.querySelectorAll(Z)].filter($);if(K.length===0)continue;Y.hidden=K.every((W)=>W.hidden)}let q=this.element.getAttribute("data-reactive-filter-empty");if(q){for(let Y of this.element.querySelectorAll(q))if($(Y))Y.hidden=z>0}}#eX(){if(!this.#Q)return;this.element.removeEventListener?.("input",this.#Q),this.element.removeEventListener?.("turbo:morph-element",this.#Q),this.#Q=void 0}#F(){return!!this.element.getAttribute?.("data-reactive-tags-field")}#XZ(){if(typeof this.element?.querySelector!=="function")return!1;return!!this.element.querySelector("[data-reactive-nested-json]")}#y(){if(typeof this.element?.querySelectorAll!=="function")return null;let X=this.element.getAttribute("data-reactive-tags-field");if(!X)return null;let Z=this.#X();return[...this.element.querySelectorAll(X)].find(Z)??null}#v(X){let Z=new Set,$=[];for(let Q of String(X.value??"").split(",")){let j=Q.trim();if(j===""||Z.has(j.toLowerCase()))continue;Z.add(j.toLowerCase()),$.push(j)}return $}#YX(X){let Z=this.#y();if(!Z)return!1;let $=this.#v(Z),Q=new Set($.map((z)=>z.toLowerCase())),j=!1;for(let z of X){let G=String(z??"").trim();if(G===""||Q.has(G.toLowerCase()))continue;Q.add(G.toLowerCase()),$.push(G),j=!0}if(j)this.#KX(Z,$);return j}#KX(X,Z){if(X.value=Z.join(","),typeof X.dispatchEvent==="function")X.dispatchEvent(new Event("input",{bubbles:!0}));this.#p()}#ZZ(){if(typeof this.element?.querySelectorAll!=="function")return null;let X=this.element.getAttribute("data-reactive-filter-input");if(!X)return null;let Z=this.#X();return[...this.element.querySelectorAll(X)].find(Z)??null}#p(){let X=this.#y();if(!X)return;let Z=this.#v(X),$=this.#X();this.#$Z(Z,$),this.#QZ(Z,$)}#$Z(X,Z){let $=[...this.element.querySelectorAll("[data-reactive-tags-list]")].find(Z);if(!$)return;let j=[...this.element.querySelectorAll("[data-reactive-tags-template]")].find(Z)?.content?.firstElementChild;if(!j){if(!this.#c)console.warn("[phlex-reactive] reactive_tags: no chip <template data-reactive-tags-template> found in this root — "+"chips will not render (the hidden field still updates). Add a template with a [data-reactive-tag-text] node and a reactive_tags_remove button."),this.#c=!0;return}while($.firstChild)$.removeChild($.firstChild);for(let z of X){let G=j.cloneNode(!0);G.setAttribute?.("data-reactive-tag",z);let q=G.matches?.("[data-reactive-tag-text]")?G:(G.querySelectorAll?.("[data-reactive-tag-text]")??[])[0];if(q)q.textContent=z;let Y=[...G.querySelectorAll?.('[data-action*="reactive#tagsRemove"]')??[]];if(G.matches?.('[data-action*="reactive#tagsRemove"]'))Y.push(G);for(let K of Y)K.setAttribute?.("data-reactive-tag-param",z);$.appendChild(G)}}#QZ(X,Z){let $=new Set(X.map((Q)=>Q.toLowerCase()));for(let Q of this.element.querySelectorAll("[role=option]")){if(!Z(Q))continue;let j=Q.getAttribute?.("data-reactive-tag-param");if(!j)continue;if($.has(j.toLowerCase()))Q.setAttribute("data-reactive-tags-selected","true"),Q.hidden=!0,Q.removeAttribute?.("data-reactive-highlighted");else if(Q.hasAttribute?.("data-reactive-tags-selected")){if(Q.removeAttribute("data-reactive-tags-selected"),!this.#R())Q.hidden=!1}}if(this.#R())this.#A()}#jZ(){return this.#k=Math.max(this.#k+1,Date.now()),this.#k}#zZ(X,Z){let $=[X,...X.querySelectorAll?.("*")??[]];for(let Q of $)for(let j of["name","id","for"]){let z=Q.getAttribute?.(j);if(z&&z.includes("NEW_ROW"))Q.setAttribute?.(j,z.replaceAll("NEW_ROW",String(Z)))}}#GZ(X){if(this.#d)return;console.warn(`[phlex-reactive] nested rows: no owned [data-reactive-nested-list="${X}"] container + <template data-reactive-nested-template="${X}"> pair found in this root — the add `+"trigger did nothing. Render both inside the same reactive root (reactive_nested_list / reactive_nested_template)."),this.#d=!0}#qZ(){if(!this.#V)return;this.element.removeEventListener?.("turbo:morph-element",this.#V),this.#V=void 0}#YZ(){if(this.#j)this.element.removeEventListener?.("input",this.#j),this.element.removeEventListener?.("change",this.#j),this.#j=void 0;if(this.#B)this.element.removeEventListener?.("turbo:morph-element",this.#B),this.#B=void 0}#KZ(){if(!this.#_)return;this.element.removeEventListener?.("turbo:morph-element",this.#_),this.#_=void 0}#HZ(X,Z,$,Q){let j=new FormData;j.append("token",X),j.append("act",Z);for(let[G,q]of Object.entries($))this.#f(j,`params[${G}]`,q);let z=this.#UZ(Q);for(let{name:G,file:q,multiple:Y}of Q){let W=Y||z.has(G)?`params[${G}][]`:`params[${G}]`;j.append(W,q,q.name)}return j}#f(X,Z,$){if($==null)X.append(Z,"");else if(Array.isArray($))$.forEach((Q,j)=>this.#f(X,`${Z}[${j}]`,Q));else if(typeof $==="object")for(let[Q,j]of Object.entries($))this.#f(X,`${Z}[${Q}]`,j);else X.append(Z,String($))}#UZ(X){let Z=new Map;for(let{name:$}of X)Z.set($,(Z.get($)??0)+1);return new Set([...Z].filter(([,$])=>$>1).map(([$])=>$))}#HX(X){if(!X)return{};try{return typeof X==="string"?JSON.parse(X):X}catch{return{}}}#UX(X){return HX(X)}#WX(X){UX(X,(Z)=>this.#JX(Z))}#JX(X){let Z=X.to;if(Z==="@root")return[this.element];if(typeof Z!=="string"||Z==="")return[];if(X.global)return[...document.querySelectorAll(Z)];return[...this.element.querySelectorAll(Z)].filter(($)=>this.#$($))}#WZ(X,Z){if(X?.checked!=="keep")return!1;let $=Z?.type;return $==="checkbox"||$==="radio"}#JZ(X,Z){if(!X)return null;let $=this.#LX(X,Z,!0);return $.length?$:null}#Y(X){if(!X)return;if(!this.element.isConnected)return;for(let Z of X)Z()}#LZ(X,Z,$){this.#BZ(X,Z);let Q=$?this.#LX($,Z,!1):[];QX();let j=!1;return()=>{if(j)return;j=!0,this.#_Z(X,Z),jX();for(let z of Q)z()}}#LX(X,Z,$){let Q=[];for(let j of this.#VX(X,Z)){if(X.add_class){let z=X.add_class.filter((G)=>!j.classList.contains(G));if(j.classList.add(...z),z.length)Q.push(()=>j.classList.remove(...z))}if(X.remove_class){let z=X.remove_class.filter((G)=>j.classList.contains(G));if(j.classList.remove(...z),z.length)Q.push(()=>j.classList.add(...z))}if(X.toggle_class)X.toggle_class.forEach((z)=>j.classList.toggle(z)),Q.push(()=>X.toggle_class.forEach((z)=>j.classList.toggle(z)));if(X.hide)j.hidden=!0,Q.push(()=>j.hidden=!1);if(X.show)j.hidden=!1,Q.push(()=>j.hidden=!0)}if(Z&&(X.disable||X.text!=null))Q.push(this.#VZ(X,Z));if($&&X.checked==="keep"&&Z&&"checked"in Z){let j=Z.checked;Q.push(()=>Z.checked=!j)}return Q}#VX(X,Z){if(X.to==null)return Z?[Z]:[];return this.#JX({to:X.to})}#VZ(X,Z){let $=this.#O.get(Z);if($)$.count++;else this.#O.set(Z,{count:1,disabled:Z.disabled,html:Z.innerHTML,hadText:X.text!=null,swappedTo:X.text});if(X.disable)Z.disabled=!0;if(X.text!=null)Z.innerHTML=X.text;return()=>this.#AZ(Z,X)}#BZ(X,Z){if(this.#K(Z,X,1),this.#K(this.element,X,1),this.#U.set(X,(this.#U.get(X)??0)+1),this.#T++===0)this.element.setAttribute("aria-busy","true");for(let $ of this.#BX(X))this.#K($,X,1)}#_Z(X,Z){this.#K(Z,X,-1),this.#K(this.element,X,-1);let $=(this.#U.get(X)??1)-1;if($<=0)this.#U.delete(X);else this.#U.set(X,$);if(--this.#T<=0)this.#T=0,this.element.removeAttribute("aria-busy");for(let Q of this.#BX(X))this.#K(Q,X,-1)}#K(X,Z,$){if(!X||typeof X.getAttribute!=="function")return;let Q=this.#E.get(X)??new Map,j=(Q.get(Z)??0)+$;if(j<=0)Q.delete(Z);else Q.set(Z,j);if(Q.size===0){this.#E.delete(X),X.removeAttribute("data-reactive-busy");return}this.#E.set(X,Q),X.setAttribute("data-reactive-busy",[...Q.keys()].join(" "))}#BX(X){return[...this.element.querySelectorAll?.("[data-reactive-busy-on]")??[]].filter(($)=>$.getAttribute("data-reactive-busy-on")===X&&this.#$($))}#AZ(X,Z){let $=this.#O.get(X);if(!$)return;if(--$.count>0)return;if(this.#O.delete(X),!X.isConnected)return;if(Z.disable)X.disabled=$.disabled;if($.hadText&&X.innerHTML===$.swappedTo)X.innerHTML=$.html}#MZ(X){if(!X||typeof X!=="object")return null;let{class:Z,...$}=X;return Z==null?X:{...$,add_class:Z}}#OZ(){return this.#_X??=document.querySelector('meta[name="phlex-reactive-action-path"]')?.content||"/reactive/actions"}#xZ(){if(this.#I!=null)return this.#I;let X=document.querySelector('meta[name="phlex-reactive-timeout"]')?.content,Z=Number(X);return this.#I=Number.isFinite(Z)&&Z>0?Z:30000}#PZ(){return document.querySelector('meta[name="csrf-token"]')?.content??""}#NZ(){return document.querySelector("pgbus-stream-source[connection-id]")?.getAttribute("connection-id")||document.querySelector('meta[name="pgbus-connection-id"]')?.content||null}}export{GZ as resetReactiveDefers,JZ as resetReactiveActivity,VX as registerReactiveVisit,BX as registerReactiveToken,pX as registerReactiveOffline,_X as registerReactiveJs,IX as registerReactiveEffects,DX as registerReactiveDismiss,AX as registerReactiveDefer,d as registerReactiveActions,WZ as reactiveActivityCount,qZ as pendingDeferVia,jX as exitReactiveActivity,gX as escapeRegExp,QX as enterReactiveActivity,fX as enableLatencySim,mX as disableLatencySim,BZ as default,cX as checkReactiveRegistration,LZ as __resetReactiveRegistrationForTest,HZ as __resetReactiveOfflineForTest,UZ as __resetReactiveLatencyForTest,KZ as __resetReactiveEffectsForTest,YZ as __resetReactiveDismissForTest,VZ as __markReactiveConnectedForTest,m as LATENCY_KEY,$X as ACTIVE_ATTR};
1
+ import{Controller as WX}from"@hotwired/stimulus";import{confirmResolver as I}from"phlex/reactive/confirm";import{computeReducer as JX}from"phlex/reactive/compute";import{confirmPredicate as LX}from"phlex/reactive/confirm_predicate";function VX(){let X=window.Turbo?.StreamActions;if(!X||X["reactive:visit"])return;X["reactive:visit"]=function(){let Z=this.getAttribute("data-url");if(Z)window.Turbo.visit(Z,{action:"advance"})}}function BX(){let X=window.Turbo?.StreamActions;if(!X||X["reactive:token"])return;X["reactive:token"]=function(){let Z=this.getAttribute("data-reactive-token-value"),$=this.getAttribute("target");if(!Z||!$)return;let Q=document.getElementById($);if(Q)Q.setAttribute("data-reactive-token-value",Z)}}function _X(){let X=window.Turbo?.StreamActions;if(!X||X["reactive:js"])return;X["reactive:js"]=function(){let Z=UX(this.getAttribute("data-reactive-ops"));if(!Z.length)return;let $=this.getAttribute("target"),Q=$?document.getElementById($):null;if($&&!Q)return;N(Z,(j)=>qZ(j,Q))}}var _=new Map;function n(X,Z){let $=!_.has(X);if(_.set(X,Z),$)jX()}function R(X){if(_.delete(X))zX()}function UZ(){_.clear(),w=!1}function WZ(X){return _.get(X)?.via}var w=!1;function MX(){let X=window.Turbo?.StreamActions;if(!X||X["reactive:defer"])return;if(X["reactive:defer"]=function(){let Z=this.getAttribute("target");if(!Z)return;if(this.getAttribute("data-reactive-defer-via")==="stream"){xX(Z,this);return}let $=this.getAttribute("data-reactive-defer-token");if(!$)return;F(Z,$)},!w&&typeof document<"u"&&document.addEventListener)w=!0,document.addEventListener("turbo:before-stream-render",AX)}function AX(X){let $=X.target?.getAttribute?.("target");if(!$)return;let Q=$.startsWith("reactive-defer-src-")?$.slice(19):$;if(_.get(Q)?.via==="stream")R(Q)}function F(X,Z){let $=document.getElementById(X);if(!$){console.warn(`[phlex-reactive] reactive:defer target #${X} is not on the page — skipped`);return}r(X),t($);let Q={via:"fetch",abort:new AbortController,timedOut:!1};n(X,Q),PX(X,Q,Z)}function xX(X,Z){let $=document.getElementById(X);if(!$){console.warn(`[phlex-reactive] reactive:defer target #${X} is not on the page — skipped`);return}let Q=Z.getAttribute("data-reactive-defer-src");if(!Q)return;if(!globalThis.customElements?.get?.("pgbus-stream-source")){let z=Z.getAttribute("data-reactive-defer-token");if(z){F(X,z);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}r(X),t($);let j=document.createElement("pgbus-stream-source");j.id=a(X),j.setAttribute("src",Q),j.setAttribute("since-id",Z.getAttribute("data-reactive-defer-since-id")??"0"),j.setAttribute("hidden",""),document.body.appendChild(j),n(X,{via:"stream"})}function a(X){return`reactive-defer-src-${X}`}async function PX(X,Z,$){let Q=setTimeout(()=>{Z.timedOut=!0,Z.abort.abort()},DX()),j;try{j=await fetch(NX(),{method:"POST",headers:{Accept:"text/vnd.turbo-stream.html","Content-Type":"application/json","X-CSRF-Token":OX()},body:JSON.stringify({token:$}),credentials:"same-origin",signal:Z.abort.signal})}catch(q){if(clearTimeout(Q),_.get(X)!==Z)return;console.error("[phlex-reactive] deferred render failed",q),T(X,$);return}if(_.get(X)!==Z){clearTimeout(Q);return}if(j.status===204){clearTimeout(Q),c(X);return}if(!j.ok){clearTimeout(Q),console.error(`[phlex-reactive] deferred render failed: HTTP ${j.status}`),T(X,$,j.status);return}let z;try{z=await j.text()}catch(q){if(clearTimeout(Q),_.get(X)!==Z)return;console.error("[phlex-reactive] deferred render failed reading the body",q),T(X,$);return}if(clearTimeout(Q),_.get(X)!==Z)return;c(X),window.Turbo.renderStreamMessage(z)}function r(X){let Z=_.get(X);if(!Z)return;if(R(X),Z.via==="fetch")Z.abort.abort();else document.getElementById(a(X))?.remove?.()}function t(X){X.setAttribute("data-reactive-defer-pending","true"),X.setAttribute("aria-busy","true")}function e(X){X.removeAttribute("data-reactive-defer-pending"),X.removeAttribute("aria-busy")}function c(X){R(X);let Z=document.getElementById(X);if(!Z)return;e(Z),Z.removeAttribute("data-reactive-error")}function T(X,Z,$){R(X);let Q=document.getElementById(X);if(!Q)return;e(Q),Q.setAttribute("data-reactive-error","defer");let j=()=>{let z=document.getElementById(X);if(!z){console.warn("[phlex-reactive] defer retry() ignored — the target left the DOM");return}z.removeAttribute("data-reactive-error"),F(X,Z)};Q.dispatchEvent(new CustomEvent("reactive:error",{bubbles:!0,composed:!0,detail:{kind:"defer",target:X,status:$,retry:j}}))}function NX(){return document.querySelector('meta[name="phlex-reactive-defer-path"]')?.content||"/reactive/defer"}function OX(){return document.querySelector('meta[name="csrf-token"]')?.content??""}function DX(){let X=document.querySelector('meta[name="phlex-reactive-timeout"]')?.content,Z=Number(X);return Number.isFinite(Z)&&Z>0?Z:30000}var h=!1;function RX(){if(h)return;if(typeof document>"u"||!document.addEventListener)return;h=!0,document.addEventListener("turbo:before-stream-render",FX)}function FX(X){let Z=X.detail,$=Z?.render;if(typeof $!=="function"||$.__reactiveDismissWrapped){if(typeof requestAnimationFrame==="function")requestAnimationFrame(E);else setTimeout(E,0);return}let Q=async(j)=>{await $(j),E()};Q.__reactiveDismissWrapped=!0,Z.render=Q}function E(){let X=document.querySelectorAll("[data-reactive-dismiss-after]");for(let Z of X){if(Z.hasAttribute("data-reactive-dismiss-scheduled"))continue;let $=Number(Z.getAttribute("data-reactive-dismiss-after"));if(!Number.isFinite($)||$<=0)continue;Z.setAttribute("data-reactive-dismiss-scheduled",""),setTimeout(()=>Z.remove(),$)}}function JZ(){h=!1}var CX=Object.freeze({append:"enter",prepend:"enter",replace:"update",update:"update",remove:"exit"}),k=Object.freeze(["fade","slide","scale","highlight","shake"]),y="data-reactive-fx-pending",XX=1000,v=!1;function IX(){if(v)return;if(typeof document>"u"||typeof document.addEventListener!=="function")return;v=!0,document.addEventListener("turbo:before-stream-render",TX)}function LZ(){v=!1}function TX(X){let Z=X.detail,$=Z?.render;if(typeof $!=="function"||$.__reactiveEffectsWrapped)return;let Q=Z?.newStream??X.target,j=CX[Q?.getAttribute?.("action")];if(!j||SX())return;let z=EX(Q,j);if(!z)return;let q=j==="exit"?async(G)=>{await hX(O(Q),z),await $(G)}:async(G)=>{let Y=j==="enter"?bX(Q):null;if(await $(G),j==="enter")wX(Y,z);else ZX(O(Q),z)};q.__reactiveEffectsWrapped=!0,Z.render=q}function EX(X,Z){let $=X.getAttribute?.("data-reactive-effect");if($==="off")return null;if($)return d($,Z);let j=(Z==="enter"?kX(X):O(X))?.getAttribute?.(`data-reactive-effect-${Z}`);return j?d(j,Z):null}function O(X){let Z=X.getAttribute?.("target");return Z?document.getElementById?.(Z)??null:null}function kX(X){return X.querySelector?.("template")?.content?.firstElementChild??null}function d(X,Z){if(X.startsWith("[")){let Q=null;try{let j=JSON.parse(X);if(Array.isArray(j)&&j.length===3)Q=j.map(String)}catch{}if(Q)return{legs:Q};return console.warn(`[phlex-reactive] malformed effect legs ${JSON.stringify(X)} — skipped`),null}let $=X==="random"?k[Math.floor(Math.random()*k.length)]:X;if(!k.includes($))return console.warn(`[phlex-reactive] unknown effect ${JSON.stringify(X)} — skipped`),null;return{className:`reactive-fx--${$}-${Z}`}}function SX(){try{return typeof matchMedia==="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches}catch{return!1}}function bX(X){let Z=X.querySelector?.("template")?.content;if(!Z)return null;for(let $ of Array.from(Z.children??[]))$.setAttribute?.(y,"");return O(X)}function wX(X,Z){if(typeof X?.querySelectorAll!=="function")return;for(let $ of Array.from(X.querySelectorAll(`[${y}]`)))$.removeAttribute(y),ZX($,Z)}async function hX(X,Z){if(!X?.classList)return;if(Z.legs){await $X(X,Z.legs);return}X.classList.add(Z.className);let $=p(X);if($<=0){X.classList.remove(Z.className);return}await u(X,$),X.classList.remove(Z.className)}function ZX(X,Z){if(!X?.classList)return;if(Z.legs){$X(X,Z.legs);return}if(X.classList.contains(Z.className))X.classList.remove(Z.className),X.offsetWidth;X.classList.add(Z.className);let $=p(X);if($<=0){X.classList.remove(Z.className);return}let Q=X.__reactiveFxToken=(X.__reactiveFxToken??0)+1;u(X,$).then(()=>{if(X.__reactiveFxToken===Q)X.classList.remove(Z.className)})}async function $X(X,Z){let[$,Q,j]=Z.map(yX),z=X.__reactiveFxToken=(X.__reactiveFxToken??0)+1;if(X.classList.remove(...$,...Q,...j),X.classList.add(...$,...Q),await vX(),X.__reactiveFxToken!==z)return;X.classList.remove(...Q),X.classList.add(...j);let q=p(X);if(q>0)await u(X,q);if(X.__reactiveFxToken!==z)return;X.classList.remove(...$,...j)}function yX(X){return String(X??"").split(/\s+/).filter(Boolean)}function p(X){if(typeof getComputedStyle!=="function")return 0;try{let Z=getComputedStyle(X),$=(z)=>String(z??"").split(",").reduce((q,G)=>Math.max(q,parseFloat(G)||0),0),Q=$(Z.animationDuration)+$(Z.animationDelay),j=$(Z.transitionDuration)+$(Z.transitionDelay);return Math.min(Math.max(Q,j)*1000,XX)}catch{return 0}}function u(X,Z){return new Promise(($)=>{let Q=!1,j=()=>{if(Q)return;Q=!0,$()};X.addEventListener?.("animationend",j,{once:!0}),X.addEventListener?.("transitionend",j,{once:!0}),setTimeout(j,Math.min(Z+50,XX))})}function vX(){return new Promise((X)=>{if(typeof requestAnimationFrame==="function")requestAnimationFrame(()=>X());else setTimeout(X,16)})}var f=!1;function fX(){if(f)return;if(typeof window>"u"||typeof document>"u")return;if(typeof window.addEventListener!=="function")return;f=!0;let X=()=>{let Z=document.documentElement;if(typeof Z?.toggleAttribute!=="function")return;Z.toggleAttribute("data-reactive-offline",globalThis.navigator?.onLine===!1)};X(),window.addEventListener("online",X),window.addEventListener("offline",X)}function VZ(){f=!1}var m="phlex-reactive:latency",D=!1;function pX(X){if(typeof sessionStorage>"u")return;sessionStorage.setItem(m,String(X))}function uX(){if(typeof sessionStorage>"u")return;sessionStorage.removeItem(m),D=!1}function mX(){if(typeof window>"u"||typeof document>"u")return;if(document.querySelector?.('meta[name="phlex-reactive-env"]')?.content!=="development")return;window.PhlexReactive={enableLatencySim:pX,disableLatencySim:uX}}function BZ(){D=!1}var QX="data-reactive-active",M=0;function jX(){if(M++,M===1)qX("reactive:busy")}function zX(){if(M===0)return;if(M--,M===0)qX("reactive:idle")}function _Z(){return M}function MZ(){M=0,(typeof document<"u"?document.documentElement:null)?.removeAttribute?.(QX)}function qX(X){if(typeof document>"u")return;let Z=document.documentElement;if(typeof Z?.toggleAttribute==="function")Z.toggleAttribute(QX,M>0);if(typeof document.dispatchEvent==="function"&&typeof CustomEvent==="function")document.dispatchEvent(new CustomEvent(X,{detail:{count:M}}))}function l(){VX(),BX(),_X(),MX(),RX(),IX(),fX(),mX()}function gX(X){return X.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}if(typeof window<"u")if(window.Turbo)l();else document.addEventListener("turbo:load",l,{once:!0});var C=!1;function cX(){if(C)return;if(typeof document>"u")return;let X=document.querySelectorAll('[data-controller~="reactive"]');if(!X||X.length===0)return;console.warn("[phlex-reactive] found "+X.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 AZ(){C=!1}function xZ(){C=!0}if(typeof window<"u"&&typeof document<"u"){let X=()=>setTimeout(cX,0);if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",X,{once:!0});else X()}var dX=new Set(["href","src","srcdoc","action","formaction","xlink:href","style"]);function lX(X){let Z=String(X).toLowerCase();return Z.startsWith("on")||dX.has(Z)}function iX(X,Z,$){let[Q,j,z]=Z;X.classList.add(Q,j),$(),requestAnimationFrame(()=>{X.classList.remove(j),X.classList.add(z)});let q=!1,G=()=>{if(q)return;q=!0,X.classList.remove(Q,z)};X.addEventListener("animationend",G,{once:!0}),setTimeout(G,350)}var i=Object.freeze({show:(X,Z)=>S(X,!1,Z),hide:(X,Z)=>S(X,!0,Z),toggle:(X,Z)=>S(X,!X.hidden,Z),add_class:(X,Z)=>X.classList.add(...Z.classes??[]),remove_class:(X,Z)=>X.classList.remove(...Z.classes??[]),toggle_class:(X,Z)=>(Z.classes??[]).forEach(($)=>X.classList.toggle($)),set_attr:(X,Z)=>{if(b(Z.name))X.setAttribute(Z.name,Z.value??"")},remove_attr:(X,Z)=>{if(b(Z.name))X.removeAttribute(Z.name)},toggle_attr:(X,Z)=>{if(!b(Z.name))return;if(X.hasAttribute(Z.name))X.removeAttribute(Z.name);else X.setAttribute(Z.name,"")},focus:(X)=>X.focus?.(),focus_first:(X)=>jZ(X)?.focus?.(),text:(X,Z)=>{let $=String(Z.value??"");if(X.textContent!==$)X.textContent=$},dispatch:(X,Z)=>{X.dispatchEvent(new CustomEvent(Z.name,{bubbles:!0,composed:!0,detail:Z.detail??{}}))},submit:(X)=>oX(X)?.requestSubmit?.()});function oX(X){if(X?.tagName==="FORM")return X;return X?.form??X?.closest?.("form")??null}function S(X,Z,$){if($?.transition)iX(X,$.transition,()=>X.hidden=Z);else X.hidden=Z}function b(X){if(!lX(X))return!0;return console.warn(`[phlex-reactive] refused client attr op on ${JSON.stringify(X)} — skipped`),!1}var GX=/^#[A-Za-z_][\w-]*$/;function sX(X){if(typeof X==="string"&&GX.test(X))return!0;return console.warn(`[phlex-reactive] refused cross-root mirror target ${JSON.stringify(X)} — skipped`),!1}function nX(X,Z){let $=X.getAttribute("data-reactive-show-equals");if($!==null)return Z===$;let Q=X.getAttribute("data-reactive-show-not");if(Q!==null)return Z!==Q;let j=X.getAttribute("data-reactive-show-in");if(j!==null){try{let z=JSON.parse(j);if(Array.isArray(z))return z.includes(Z)}catch{}return console.warn(`[phlex-reactive] malformed reactive_show in: list ${JSON.stringify(j)} — skipped`),null}for(let z of YX){let q=X.getAttribute(`data-reactive-show-${z}`);if(q!==null)return KX(z,q,Z)}return console.warn("[phlex-reactive] a reactive_show binding declares no predicate — skipped"),null}var YX=["gte","gt","lte","lt"],aX=["len_eq","len_gte","len_gt","len_lte","len_lt"];function rX(X,Z,$){if(!Number.isInteger(Z))return console.warn(`[phlex-reactive] reactive_show ${X}: needs an integer literal, got ${JSON.stringify(Z)} — skipped`),null;let Q=[...String($??"")].length;switch(X){case"len_eq":return Q===Z;case"len_gte":return Q>=Z;case"len_gt":return Q>Z;case"len_lte":return Q<=Z;case"len_lt":return Q<Z;default:return null}}function KX(X,Z,$){let Q=Number(Z);if(Number.isNaN(Q))return console.warn(`[phlex-reactive] reactive_show ${X}: needs a numeric literal, got ${JSON.stringify(Z)} — skipped`),null;let j=$==null?"":String($).trim(),z=j===""?NaN:Number(j);if(Number.isNaN(z))return!1;switch(X){case"gte":return z>=Q;case"gt":return z>Q;case"lte":return z<=Q;case"lt":return z<Q;default:return null}}function HX(X,Z){if(!X||typeof X!=="object")return null;if(typeof X.equals==="string")return Z===X.equals;if(typeof X.not==="string")return Z!==X.not;if(Array.isArray(X.in))return X.in.includes(Z);for(let $ of YX)if($ in X)return KX($,X[$],Z);for(let $ of aX)if($ in X)return rX($,X[$],Z);return null}var o="[data-reactive-show-field], [data-reactive-show]";function tX(X){try{let Z=JSON.parse(X);if(Z&&typeof Z==="object"&&!Array.isArray(Z))return Z}catch{}return console.warn(`[phlex-reactive] malformed compound reactive_show payload ${JSON.stringify(X)} — skipped`),null}function eX(X){try{let Z=JSON.parse(X);if(Array.isArray(Z)&&Z.every(($)=>$&&typeof $==="object"&&Array.isArray($.any)&&Array.isArray($.ops)))return Z}catch{}return console.warn(`[phlex-reactive] malformed reactive_on_complete payload ${JSON.stringify(X)} — skipped`),[]}function g(X,Z){if(!X||typeof X!=="object"||typeof X.field!=="string")return!1;let $=Z(X.field)??"";return HX(X,$)===!0}function P(X,Z){if(!Array.isArray(X)||X.length===0)return null;return X.some(($)=>Array.isArray($)&&$.length>0&&$.every((Q)=>g(Q,Z)))}function XZ(X){if(!Array.isArray(X)||X.length===0)return null;let Z=new Set;for(let $ of X){if(!Array.isArray($))continue;for(let Q of $)if(Q&&typeof Q==="object"&&typeof Q.field==="string")Z.add(Q.field)}return Z.size>0?[...Z]:null}function ZZ(X,Z){if(!X||typeof X!=="object")return null;let $=X.any;if(Array.isArray($)&&($.length===0||Array.isArray($[0])))return P($,Z);return $Z(X,Z)}function $Z(X,Z){let $=Array.isArray(X.all)?"all":Array.isArray(X.any)?"any":null;if(!$)return null;let Q=X[$];if(Q.length===0)return null;let j=Q.map((z)=>g(z,Z));return $==="all"?j.every(Boolean):j.some(Boolean)}function s(X){if(typeof X==="string"&&GX.test(X))return!0;return console.warn(`[phlex-reactive] refused cross-root show target ${JSON.stringify(X)} — skipped`),!1}var QZ='a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';function jZ(X){return X.querySelectorAll?.(QZ)?.[0]??null}function UX(X){if(Array.isArray(X))return X;if(typeof X!=="string")return[];try{let Z=JSON.parse(X);return Array.isArray(Z)?Z:[]}catch{return[]}}function zZ(X){if(X==null)return null;let Z=Array.isArray(X)?X:X.ops;if(Array.isArray(Z))return Z.length>0?Z:null;return console.warn("[phlex-reactive] $ops must be an ops chain or a [[op, args], ...] list — skipped"),null}function N(X,Z){for(let $ of X){if(!Array.isArray($))continue;let[Q,j={}]=$;if(!Object.hasOwn(i,Q)){console.warn(`[phlex-reactive] unknown client op ${JSON.stringify(Q)} — skipped`);continue}for(let z of Z(j))i[Q](z,j)}}function qZ(X,Z){let $=X.to;if(Z){if($==="@root")return[Z];if(typeof $!=="string"||$==="")return[];if(X.global)return[...document.querySelectorAll($)];return[...Z.querySelectorAll($)]}if(typeof $!=="string"||$===""||$==="@root")return[];return[...document.querySelectorAll($)]}class PZ extends WX{static values={token:String};#i;#x=new Map;#U=new Map;#RX;#E;#o;#k=0;#W=new Map;#S=new WeakMap;#P=new Map;#s=new WeakSet;#n=null;#J;#L;#V;#Z;#z;#b;#a;#w;#h;#Q;#B;#r=!1;#y=0;#t=!1;#j;#_;#M;#N;connect(){if(C=!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.#e(),this.#N=()=>this.#e(),this.element.addEventListener?.("turbo:morph-element",this.#N);if(this.#FX()){if(this.#J=()=>this.#u(),this.element.addEventListener?.("turbo:morph-element",this.#J),this.#u(),this.element.getAttribute?.("data-reactive-warn-unsaved")==="true")this.#tX()}if(this.#XZ())this.#Z=()=>this.#LX(),this.element.addEventListener?.("input",this.#Z),this.element.addEventListener?.("change",this.#Z),this.element.addEventListener?.("turbo:morph-element",this.#Z),this.#LX();if(this.#ZZ())this.#z=(X)=>this.#m(X),this.#b=()=>this.#m(null),this.element.addEventListener?.("input",this.#z),this.element.addEventListener?.("change",this.#z),this.element.addEventListener?.("turbo:morph-element",this.#b),this.#m(null);if(this.#C())this.#Q=(X)=>{if(X?.type==="input"&&!this.#KZ(X))return;this.#A()},this.element.addEventListener?.("input",this.#Q),this.element.addEventListener?.("turbo:morph-element",this.#Q),this.#A();if(this.#I())this.#B=()=>this.#d(),this.element.addEventListener?.("turbo:morph-element",this.#B),this.#d();if(this.#UZ())this.#j=(X)=>this.syncNestedJson(X),this.#_=()=>this.#D(),this.element.addEventListener?.("input",this.#j),this.element.addEventListener?.("change",this.#j),this.element.addEventListener?.("turbo:morph-element",this.#_),this.#D();if(this.#YZ())this.#M=()=>this.recompute(),this.element.addEventListener?.("turbo:morph-element",this.#M),this.recompute()}#FX(){if((this.element.getAttribute?.("data-action")??"").includes("reactive#trackDirty"))return!0;let X=this.element.querySelectorAll?.('[data-action*="reactive#trackDirty"]')??[];for(let Z of X)if(this.#$(Z))return!0;return!1}disconnect(){if(this.#mX(),this.#cX(),this.#eX(),this.#GZ(),this.#QZ(),this.#HZ(),this.#MZ(),this.#AZ(),this.#xZ(),this.#N)this.element.removeEventListener?.("turbo:morph-element",this.#N)}#e(){let X=this.element;if(!X?.id)return;let Z=X.getAttribute?.("data-reactive-defer-token");if(!Z)return;if(X.getAttribute?.("data-reactive-defer-pending")!=="true")return;F(X.id,Z)}dispatch(X){let{action:Z,params:$,debounce:Q,throttle:j,confirm:z,confirmWhen:q,outside:G,window:Y,optimistic:K}=X.params;if(!Z)return;let U=X.params.busy??this.#EZ(X.params.loading);if(G&&this.element.contains(X.target))return;let B=X.currentTarget??X.target;if(!Y&&!this.#OZ(K,B))X.preventDefault();let W=this.#p(z,q);if(!W)return this.#GX(B,Z,$,Q,j,K,U);Promise.resolve().then(()=>I(W,{el:B})).catch(()=>!1).then((V)=>{if(V)this.#GX(B,Z,$,Q,j,K,U)})}runOps(X){let{ops:Z,confirm:$,confirmWhen:Q,outside:j,window:z}=X.params,q=X.currentTarget??X.target;if(j&&this.element.contains(X.target))return;if(!z)X.preventDefault();let G=this.#p($,Q);if(!G)return this.#PX(this.#xX(Z));Promise.resolve().then(()=>I(G,{el:q})).catch(()=>!1).then((Y)=>{if(Y)this.#PX(this.#xX(Z))})}trackDirty(){this.#u()}recompute(X){if(X&&this.#s.has(X))return;let Z=this.#wX(),$=Z.map(([H])=>H),Q=this.element.getAttribute?.("data-reactive-scope")||null,j=(H)=>Q&&!H.includes("[")?`${Q}[${H}]`:H,z=this.#X(),q=new Map,G=(H)=>{if(q.has(H))return q.get(H);let J=null;for(let L of this.element.querySelectorAll(`[name="${j(H)}"]`))if(z(L)){J=L;break}return q.set(H,J),J};for(let H of $)this.#zX(H,G(H)?.value??"");let Y=this.element.getAttribute("data-reactive-compute-reducer-param"),K=Y?JX(Y):null;if(!K){this.#qX({},G);return}let U=this.#bX("data-reactive-compute-outputs-param"),B={};for(let[H,J]of Z){let L=G(H);if(J==="string")B[H]=L?.value??"";else{let A=Number(L?.value);B[H]=Number.isFinite(A)?A:0}}let W=K(B,{changed:this.#hX(X,$,Q)})||{},V=zZ(W.$ops),x=[];for(let H of U){if(H==="$ops"||!(H in W))continue;let J=G(H);if(!J)continue;if(String(W[H])===J.value)continue;J.value=W[H],x.push(J)}for(let H of Object.keys(W)){if(H==="$ops")continue;let J=W[H];if(J===void 0||J===null)continue;this.#zX(H,J)}this.#qX(W,G);for(let H of x){let J=new Event("input",{bubbles:!0});this.#s.add(J),H.dispatchEvent(J)}this.#CX(V,Boolean(X))}#CX(X,Z){let $=X===null?null:JSON.stringify(X),Q=$!==null&&$!==this.#n&&Z;if(this.#n=$,!Q)return;N(X,(j)=>this.#T(j.to==null?{...j,to:"@root"}:j))}listnavNext(X){this.#XX(X,1)}listnavPrev(X){this.#XX(X,-1)}listnavPick(X){let Z=this.#O(X),$=Z.findIndex((Q)=>Q.hasAttribute("data-reactive-highlighted"));if($<0)return;X.preventDefault(),Z[$].click()}listnavClose(X){for(let Z of this.#O(X))Z.removeAttribute("data-reactive-highlighted")}#XX(X,Z){let $=this.#O(X);if(!$.length)return;X.preventDefault();let Q=$.findIndex((q)=>q.hasAttribute("data-reactive-highlighted")),j=Q<0?Z>0?0:$.length-1:(Q+Z+$.length)%$.length;for(let q of $)q.removeAttribute("data-reactive-highlighted");let z=$[j];z.setAttribute("data-reactive-highlighted","true"),z.scrollIntoView?.({block:"nearest"})}#O(X){let $=(X?.currentTarget??X?.target??this.element).getAttribute?.("data-reactive-listnav-option-param")??this.element.getAttribute("data-reactive-listnav-option-param");if(!$)return[];let Q=this.#X();return Array.from(this.element.querySelectorAll($)).filter((j)=>!j.hidden&&Q(j))}tagsAdd(X){if(!this.#I())return;if(X?.defaultPrevented)return;if(this.#O(X).some((Q)=>Q.hasAttribute?.("data-reactive-highlighted")))return;X?.preventDefault?.();let Z=X?.currentTarget??X?.target;if(!Z)return;if(!this.#_X(String(Z.value??"").split(",")))return;if(Z.value="",this.#C())this.#A()}tagsPick(X){if(!this.#I())return;X?.preventDefault?.();let $=(X?.currentTarget??X?.target)?.getAttribute?.("data-reactive-tag-param");if(!$)return;if(!this.#_X([$]))return;let Q=this.#WZ();if(!Q)return;Q.value="",this.#A(),Q.focus?.()}tagsRemove(X){if(!this.#I())return;X?.preventDefault?.();let $=(X?.currentTarget??X?.target)?.getAttribute?.("data-reactive-tag-param");if(!$)return;let Q=this.#g();if(!Q)return;let j=this.#c(Q),z=j.filter((q)=>q.toLowerCase()!==$.toLowerCase());if(z.length===j.length)return;this.#MX(Q,z)}nestedAdd(X){X?.preventDefault?.();let Z=X?.currentTarget??X?.target,$=Z?.getAttribute?.("data-reactive-association-param");if(!$)return;if(typeof this.element?.querySelectorAll!=="function")return;let Q=this.#X(),j=[...this.element.querySelectorAll(`[data-reactive-nested-list="${$}"]`)].find(Q),q=[...this.element.querySelectorAll(`[data-reactive-nested-template="${$}"]`)].find(Q)?.content?.firstElementChild;if(!j||!q){this.#_Z($);return}let G=q.cloneNode(!0);this.#BZ(G,this.#VZ()),j.appendChild(G);let Y=Z?.getAttribute?.("data-reactive-nested-from-param"),K=Z?.getAttribute?.("data-reactive-nested-clear-param")==="true",U=this.#IX(G,Y,K);if(Y)U?.focus?.();else[...G.querySelectorAll?.("input, select, textarea")??[]][0]?.focus?.();if(j.getAttribute?.("data-reactive-nested-json")===$)this.#$X($)}#IX(X,Z,$){if(!Z)return null;let Q;try{Q=JSON.parse(Z)}catch{return null}if(!Q||typeof Q!=="object")return null;let j=this.#X(),z=[...X.querySelectorAll?.("input, select, textarea")??[]],q=[];for(let[G,Y]of Object.entries(Q)){let K=[...this.element.querySelectorAll?.(Y)??[]].find(j);if(!K)continue;let U=z.find((B)=>this.#jX(B.getAttribute?.("name"))===G);if(!U)continue;this.#TX(U,K),q.push(K)}if($)for(let G of q)this.#EX(G);return q[0]??null}#TX(X,Z){if(X.type==="checkbox")X.checked=Z.type==="checkbox"?!!Z.checked:this.#v(Z)!=="";else X.value=this.#v(Z);if(typeof X.dispatchEvent==="function")X.dispatchEvent(new Event("input",{bubbles:!0}))}#EX(X){if(X.type==="checkbox")X.checked=!1;else X.value="";if(typeof X.dispatchEvent==="function")X.dispatchEvent(new Event("input",{bubbles:!0}))}nestedRemove(X){X?.preventDefault?.();let Z=X?.currentTarget??X?.target,$=Z?.closest?.("[data-reactive-nested-row]");if(!$)return;if($.closest?.('[data-controller~="reactive"]')!==this.element)return;let Q=Z?.getAttribute?.("data-reactive-confirm-param"),j=Z?.getAttribute?.("data-reactive-confirm-when-param"),z=this.#p(Q,j);if(!z)return this.#ZX($);let q=this.#QX($),G=this.#kX(z,q);return Promise.resolve().then(()=>I(G,{el:Z,row:$,fields:q})).catch(()=>!1).then((Y)=>{if(Y)this.#ZX($)})}#kX(X,Z){if(!X.includes("%{"))return X;return X.replace(/%\{(\w+)\}/g,($,Q)=>Object.prototype.hasOwnProperty.call(Z,Q)?Z[Q]:$)}#ZX(X){let Z=[...X.querySelectorAll?.('input[name$="[_destroy]"]')??[]][0];if(Z){if(Z.value="1",typeof Z.dispatchEvent==="function")Z.dispatchEvent(new Event("input",{bubbles:!0}));X.hidden=!0}else X.parentNode?.removeChild?.(X);this.#D()}syncNestedJson(X){let Z=X?.target;if(!Z||!this.#$(Z))return;this.#D()}#D(){if(typeof this.element?.querySelectorAll!=="function")return;let X=this.#X();for(let Z of[...this.element.querySelectorAll("[data-reactive-nested-json]")].filter(X))this.#$X(Z.getAttribute("data-reactive-nested-json"))}#$X(X){let Z=this.#X(),$=[...this.element.querySelectorAll(`[data-reactive-nested-list="${X}"]`)].find(Z);if(!$)return;let Q=this.#SX($);if(!Q)return;let j=[];for(let q of[...$.querySelectorAll?.("[data-reactive-nested-row]")??[]]){if(!Z(q)||q.hidden)continue;j.push(this.#QX(q))}let z=JSON.stringify(j);if(Q.value===z)return;if(Q.value=z,typeof Q.dispatchEvent==="function")Q.dispatchEvent(new Event("input",{bubbles:!0}))}#SX(X){let Z=X.getAttribute?.("data-reactive-nested-json-field");if(!Z)return null;let $=this.#X();return[...this.element.querySelectorAll(Z)].find($)??null}#QX(X){let Z={};for(let $ of[...X.querySelectorAll?.("input, select, textarea")??[]]){let Q=this.#jX($.getAttribute?.("name"));if(Q===null||Q==="_destroy")continue;Z[Q]=this.#v($)}return Z}#jX(X){if(!X)return null;let Z=X.match(/\[([^\][]+)\]$/);return Z?Z[1]:X}#v(X){if(X.type==="checkbox")return X.checked?X.value||"on":"";return X.value??""}#bX(X){let Z=this.element.getAttribute(X);if(!Z)return[];try{let $=JSON.parse(Z);return Array.isArray($)?$:[]}catch{return[]}}#wX(){let X=this.element.getAttribute("data-reactive-compute-inputs-param");if(!X)return[];try{let Z=JSON.parse(X);if(Array.isArray(Z))return Z.map(($)=>[$,"number"]);if(Z&&typeof Z==="object")return Object.entries(Z);return[]}catch{return[]}}#hX(X,Z,$){let Q=X?.target;if(!Q?.name||typeof Q.closest!=="function")return null;let j=this.#yX(Q.name,$);if(!Z.includes(j))return null;return this.#$(Q)?j:null}#yX(X,Z){if(!Z)return X;let $=`${Z}[`;return X.startsWith($)&&X.endsWith("]")?X.slice($.length,-1):X}#zX(X,Z){let $=String(Z);for(let Q of this.#vX(X)){if(Q.textContent===$)continue;Q.textContent=$}}#vX(X){let Z=this.element.querySelectorAll(`[data-reactive-text="${X}"]`);return Array.from(Z).filter(($)=>this.#$($))}#qX(X,Z){let $=this.#fX();for(let[Q,j]of Object.entries($)){let z=Q in X?X[Q]:Z(Q)?.value;if(z===void 0||z===null)continue;let q=String(z);for(let G of Array.isArray(j)?j:[j]){if(!sX(G))continue;for(let Y of document.querySelectorAll(G)){if(Y.textContent===q)continue;Y.textContent=q}}}}#fX(){let X=this.element.getAttribute("data-reactive-compute-mirror-param");if(!X)return{};try{let Z=JSON.parse(X);return Z&&typeof Z==="object"&&!Array.isArray(Z)?Z:{}}catch{return{}}}#GX(X,Z,$,Q,j,z,q){if(this.#F("reactive:before-dispatch",{action:Z,params:this.#AX($),element:this.element},{cancelable:!0}).defaultPrevented)return;let Y=Number(Q)||0;if(Y>0)return this.#uX(X,Y,Z,$,z,q);let K=Number(j)||0;if(K>0)return this.#gX(X,K,Z,$,z,q);return this.#R(Z,$,z,X,q)}#R(X,Z,$,Q,j){let z=this.#DZ($,Q),q=this.#RZ(X,Q,j),G=this.#YX()?this.#pX($,Q):null;return this.queue=(this.queue??Promise.resolve()).then(()=>this.#nX(X,Z,z,q,G)),this.queue}#pX(X,Z){if(!X?.hide)return null;let $=this.#OX(X,Z);if(!$.length)return null;return()=>{let Q=$.filter((j)=>j.isConnected&&!j.hidden);if(!Q.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.",Q)}}#uX(X,Z,$,Q,j,z){this.#f(X);let q=()=>{this.#f(X),this.#R($,Q,j,X,z)},G=setTimeout(q,Z);X?.addEventListener?.("blur",q,{once:!0}),this.#x.set(X,{timer:G,flush:q})}#f(X){let Z=this.#x.get(X);if(!Z)return;clearTimeout(Z.timer),X?.removeEventListener?.("blur",Z.flush),this.#x.delete(X)}#mX(){for(let X of[...this.#x.keys()])this.#f(X)}#gX(X,Z,$,Q,j,z){let q=this.#U.get(X)??new Map;if(q.has($))return;let G=setTimeout(()=>{if(q.delete($),q.size===0)this.#U.delete(X)},Z);return q.set($,G),this.#U.set(X,q),this.#R($,Q,j,X,z)}#cX(){for(let X of this.#U.values())for(let Z of X.values())clearTimeout(Z);this.#U.clear()}#F(X,Z,{cancelable:$=!1}={}){let Q=new CustomEvent(X,{bubbles:!0,composed:!0,cancelable:$,detail:Z});return(this.element.isConnected?this.element:document).dispatchEvent(Q),Q}#q(X,Z,$,Q){let j=()=>{if(!this.element.isConnected){console.warn("[phlex-reactive] retry() ignored — the reactive root left the DOM");return}return this.#R(X,Z)};this.#F("reactive:error",{action:X,params:$,...Q,retry:j})}#G(X){if(this.element?.isConnected===!1)return;this.element?.setAttribute?.("data-reactive-error",X)}#dX(){this.element?.removeAttribute?.("data-reactive-error")}#lX(){let X=document.querySelector("[data-reactive-error-flash]");if(!X?.content)return;let Z=X.getAttribute("data-reactive-error-flash")||"flash",$=document.getElementById(Z);if(!$)return;$.appendChild(X.content.cloneNode(!0))}#iX(){if(typeof sessionStorage>"u")return Promise.resolve();let X=Number(sessionStorage.getItem(m));if(!Number.isFinite(X)||X<=0)return Promise.resolve();if(!D)D=!0,console.warn(`[phlex-reactive] latency simulator ACTIVE — every action is delayed by ${X}ms. Call PhlexReactive.disableLatencySim() (or clear sessionStorage) to turn it off.`);return new Promise((Z)=>setTimeout(Z,X))}#YX(){return this.element?.getAttribute?.("data-reactive-debug")==="true"}#KX(){return typeof performance<"u"&&typeof performance.now==="function"?performance.now():Date.now()}#oX(X){if(!X)return[];let Z=[],$=/<turbo-stream\b([^>]*)>/g,Q;while((Q=$.exec(X))!==null){let j=Q[1],z=j.match(/\baction="([^"]*)"/)?.[1]??"?",q=j.match(/\btarget="([^"]*)"/)?.[1];Z.push(q?`${z} → #${q}`:z)}return Z}#sX(X){let{action:Z,status:$,ms:Q}=X,z=`reactive ${this.element?.id?`#${this.element.id} `:""}${Z} → ${$??"—"} (${Math.round(Q)}ms)`;if(console.groupCollapsed(z),console.log(`params: [${X.paramNames.join(", ")}] + collected: [${X.fieldNames.join(", ")}]`),console.log(`encoding: ${X.encoding}`),X.streams.length)console.log(`streams: ${X.streams.join(" ")}`);console.log(`token: ${X.tokenRefreshed?"refreshed ✓":"unchanged"}`),console.groupEnd()}async#nX(X,Z,$,Q,j){let{fields:z,files:q}=this.#WX(),G=this.#AX(Z),Y={...z,...G},K=this.#Y,U=q.length>0,B=U?this.#PZ(K,X,Y,q):JSON.stringify({token:K,act:X,params:Y}),W=this.#YX()?{action:X,paramNames:Object.keys(G),fieldNames:Object.keys(z),encoding:U?"multipart":"json",status:null,streams:[],tokenRefreshed:!1,started:this.#KX()}:null;await this.#iX();try{if(navigator.onLine===!1){this.#K($),this.#G("offline"),this.#q(X,Z,Y,{kind:"offline"});return}let V;try{let L={Accept:"text/vnd.turbo-stream.html","X-CSRF-Token":this.#bZ()};if(!U)L["Content-Type"]="application/json";let A=this.#wZ();if(A)L["X-Pgbus-Connection"]=A;V=await fetch(this.#kZ(),{method:"POST",headers:L,body:B,credentials:"same-origin",signal:AbortSignal.timeout(this.#SZ())})}catch(L){if(console.error("[phlex-reactive] action error",L),this.#K($),L?.name==="TimeoutError"||L?.name==="AbortError"){this.#G("timeout"),this.#q(X,Z,Y,{kind:"timeout"});return}this.#lX(),this.#G("network"),this.#q(X,Z,Y,{kind:"network"});return}if(W)W.status=V.status;if(V.redirected){console.error("[phlex-reactive] action was redirected (auth/CSRF?) — no update applied"),this.#K($),this.#G("redirected"),this.#q(X,Z,Y,{kind:"redirected",status:V.status});return}if(!V.ok){let L=await V.text();if(console.error(`[phlex-reactive] action failed: HTTP ${V.status}`,L),this.#K($),(V.headers.get("Content-Type")||"").includes("turbo-stream")){let A=this.#UX(L);if(this.#Y=A??this.#Y,W)this.#HX(W,L,A);window.Turbo.renderStreamMessage(L)}this.#G("http"),this.#q(X,Z,Y,{kind:"http",status:V.status,body:L});return}let x=V.headers.get("Content-Type")||"";if(!x.includes("turbo-stream")){console.error(`[phlex-reactive] expected a turbo-stream, got "${x}" — no update applied`),this.#K($),this.#G("content-type"),this.#q(X,Z,Y,{kind:"content-type",status:V.status});return}let H=await V.text(),J=this.#UX(H);if(this.#Y=J??this.#Y,W)this.#HX(W,H,J);if(window.Turbo.renderStreamMessage(H),j)queueMicrotask(j);this.#dX(),this.#F("reactive:applied",{action:X,params:Y,html:H})}catch(V){console.error("[phlex-reactive] action error",V),this.#K($),this.#F("reactive:error",{action:X,params:Y,kind:"apply"})}finally{if(Q?.(),W)this.#sX({...W,ms:this.#KX()-W.started})}}#HX(X,Z,$){X.streams=this.#oX(Z),X.tokenRefreshed=$!=null}get#Y(){return this.#i??this.tokenValue}set#Y(X){this.#i=X}#UX(X){let Z=this.element.id;if(!Z)return X.match(/data-reactive-token-value="([^"]+)"/)?.[1];let{token:$,self:Q}=this.#aX(Z),j=X.match($);if(j)return j[1];let z=X.match(Q);if(z)return z[1].match(/data-reactive-token-value="([^"]+)"/)?.[1];return}#aX(X){let Z=this.#o;if(Z&&Z.id===X)return Z;let $=gX(X);return this.#o={id:X,token:new RegExp(`<turbo-stream\\b[^>]*\\baction="reactive:token"[^>]*\\btarget="${$}"[^>]*\\bdata-reactive-token-value="([^"]+)"`),self:new RegExp(`<turbo-stream\\b[^>]*\\baction="(?:replace|update)"[^>]*\\btarget="${$}"[^>]*>([\\s\\S]*?)</turbo-stream>`)}}#$(X){return X.closest('[data-controller~="reactive"]')===this.element}#X(){if(this.element.querySelectorAll('[data-controller~="reactive"]').length===0)return()=>!0;return(Z)=>this.#$(Z)}#p(X,Z){if(X)return X;if(!Z)return null;let $=Z;if(typeof Z==="string")try{$=JSON.parse(Z)}catch{return console.warn(`[phlex-reactive] malformed conditional confirm payload ${JSON.stringify(Z)} — skipped`),null}if(!$||typeof $!=="object")return null;let{fields:Q}=this.#WX(),j=(q)=>Q[q],z;if(typeof $.predicate==="string"){let q=LX($.predicate);if(!q)return console.warn(`[phlex-reactive] confirm predicate "${$.predicate}" is not registered — proceeding without a dialog (register it with setConfirmPredicate)`),null;z=!!q(Q)}else z=P($.groups?.any,j)===!0;return z?$.message:null}#WX(){let X={},Z=[],$=this.#X();return this.element.querySelectorAll("input[name], select[name], textarea[name]").forEach((Q)=>{if(!$(Q))return;if(Q.type==="file")for(let j of Q.files??[])Z.push({name:Q.name,file:j,multiple:Q.multiple});else if(Q.type==="checkbox")X[Q.name]=Q.checked;else if(Q.type==="radio"){if(Q.checked)X[Q.name]=Q.value}else X[Q.name]=Q.value}),this.element.querySelectorAll("[name]:is(lexxy-editor, trix-editor, [contenteditable=''], [contenteditable=true], [contenteditable=plaintext-only])").forEach((Q)=>{if(!$(Q))return;let j=Q.getAttribute("name");if(!j)return;let z=X[j];if(z==null||z==="")X[j]=Q.value??Q.textContent??Q.innerHTML??""}),{fields:X,files:Z}}#u(){if(typeof this.element?.querySelectorAll!=="function")return;let X=0;if(this.element.querySelectorAll("input[name], select[name], textarea[name]").forEach((Z)=>{if(!this.#$(Z))return;if(Z.type==="file")return;if(this.#rX(Z))Z.setAttribute("data-reactive-dirty","true"),X++;else Z.removeAttribute("data-reactive-dirty")}),X>0)this.element.setAttribute("data-reactive-dirty",String(X));else this.element.removeAttribute("data-reactive-dirty")}#rX(X){if(X.type==="checkbox"||X.type==="radio")return X.checked!==X.defaultChecked;if(X.tag==="select"||X.options)return Array.from(X.options??[]).some((Z)=>Z.selected!==Z.defaultSelected);return X.value!==X.defaultValue}#JX(){let X=this.element.getAttribute?.("data-reactive-dirty"),Z=Number(X);return Number.isFinite(Z)&&Z>0?Z:0}#tX(){if(typeof window>"u"||typeof window.addEventListener!=="function")return;this.#L=(X)=>{if(this.#JX()===0)return;return X.preventDefault(),X.returnValue="You have unsaved changes.",X.returnValue},this.#V=(X)=>{if(this.#JX()===0)return;if(!(typeof window.confirm==="function"?window.confirm("You have unsaved changes. Leave anyway?"):!0))X.preventDefault?.()},window.addEventListener("beforeunload",this.#L),window.addEventListener("turbo:before-visit",this.#V)}#eX(){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.#L)window.removeEventListener("beforeunload",this.#L);if(this.#V)window.removeEventListener("turbo:before-visit",this.#V)}this.#L=void 0,this.#V=void 0}#XZ(){if(this.element.getAttribute?.("data-reactive-show-targets"))return!0;let X=this.element.querySelectorAll?.(o)??[];for(let Z of X)if(this.#$(Z))return!0;return!1}#ZZ(){return!!this.element.getAttribute?.("data-reactive-on-complete")}#$Z(){let X=this.element.getAttribute?.("data-reactive-on-complete")??null;if(X!==this.#a)this.#a=X,this.#w=X==null?[]:eX(X),this.#h=this.#w.map(()=>!1);return this.#w}#m(X){let Z=this.#$Z();if(!Z.length)return;let $=this.#X(),Q=this.element.getAttribute?.("data-reactive-scope")||null,j=new Map,z=(q)=>{if(!j.has(q))j.set(q,this.#BX(q,$,Q));return j.get(q)};Z.forEach((q,G)=>{let Y=P(q.any,z);if(Y===null)return;let K=Y&&!this.#h[G]&&Boolean(X);if(this.#h[G]=Y,K)N(q.ops,(U)=>this.#T(U.to==null?{...U,to:"@root"}:U))})}#QZ(){if(!this.#z)return;this.element.removeEventListener?.("input",this.#z),this.element.removeEventListener?.("change",this.#z),this.element.removeEventListener?.("turbo:morph-element",this.#b)}#LX(){if(typeof this.element?.querySelectorAll!=="function")return;let X=this.#X(),Z=this.element.getAttribute?.("data-reactive-scope")||null,$=new Map,Q=(j)=>{if(!$.has(j))$.set(j,this.#BX(j,X,Z));return $.get(j)};for(let j of this.element.querySelectorAll(o)){if(!X(j))continue;let z=j.getAttribute("data-reactive-show");if(z!==null){let K=ZZ(tX(z),Q);if(K!==null)this.#VX(j,K,X,Z);continue}let q=j.getAttribute("data-reactive-show-field");if(!q)continue;let G=Q(q);if(G===null)continue;let Y=nX(j,G);if(Y===null)continue;this.#VX(j,Y,X,Z)}this.#jZ(Q)}#VX(X,Z,$,Q){if(X.hidden=!Z,X.getAttribute("data-reactive-show-disable")!=="true")return;if(typeof X.querySelectorAll!=="function")return;for(let j of X.querySelectorAll("input[name], select[name], textarea[name]"))if($(j))j.disabled=!Z;if(X.name&&$(X))X.disabled=!Z}#jZ(X){let Z=this.#qZ();for(let[$,Q]of Object.entries(Z)){if($.startsWith("#")){this.#zZ($,Q,X);continue}if(!Q||typeof Q!=="object"||Array.isArray(Q))continue;let j=X($);if(j===null)continue;let z=()=>j;for(let[q,G]of Object.entries(Q)){if(!s(q))continue;let Y;if(Array.isArray(G)){if(G.length===0){console.warn(`[phlex-reactive] malformed reactive_show_targets group for ${q} — skipped`);continue}Y=G.every((K)=>g(K,z))}else{let K=HX(G,j);if(K===null){console.warn(`[phlex-reactive] malformed reactive_show_targets predicate for ${q} — skipped`);continue}Y=K}for(let K of document.querySelectorAll(q))K.hidden=!Y}}}#zZ(X,Z,$){if(!s(X))return;let Q=Z&&typeof Z==="object"&&!Array.isArray(Z)?Z.any:null,j=XZ(Q);if(j===null){console.warn(`[phlex-reactive] malformed reactive_show_targets conditions for ${X} — skipped`);return}if(j.every((q)=>$(q)===null))return;let z=P(Q,$);if(z===null)return;for(let q of document.querySelectorAll(X))q.hidden=!z}#qZ(){let X=this.element.getAttribute?.("data-reactive-show-targets");if(!X)return{};try{let Z=JSON.parse(X);if(Z&&typeof Z==="object"&&!Array.isArray(Z))return Z}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: { ... })"),{}}#BX(X,Z,$){let Q=$&&!X.includes("[")?`${$}[${X}]`:X,j=!1,z=null;for(let q of this.element.querySelectorAll(`[name="${Q}"]`)){if(!Z(q))continue;if(q.type==="checkbox")return q.checked?"true":"false";if(q.type==="radio"){if(q.checked)return q.value??"";j=!0;continue}z??=q}if(z)return z.value??"";return j?"":null}#GZ(){if(!this.#Z)return;this.element.removeEventListener?.("input",this.#Z),this.element.removeEventListener?.("change",this.#Z),this.element.removeEventListener?.("turbo:morph-element",this.#Z),this.#Z=void 0}#C(){return!!(this.element.getAttribute?.("data-reactive-filter-input")&&this.element.getAttribute?.("data-reactive-filter-option"))}#YZ(){return this.element.getAttribute?.("data-reactive-compute-seed")==="true"}#KZ(X){let Z=this.element.getAttribute("data-reactive-filter-input");return!!Z&&typeof X.target?.matches==="function"&&X.target.matches(Z)}#A(){if(typeof this.element?.querySelectorAll!=="function")return;let X=this.element.getAttribute("data-reactive-filter-input"),Z=this.element.getAttribute("data-reactive-filter-option");if(!X||!Z)return;let $=this.#X(),Q=[...this.element.querySelectorAll(X)].find($);if(!Q)return;let j=(Q.value??"").trim().toLowerCase(),z=0;for(let Y of this.element.querySelectorAll(Z)){if(!$(Y))continue;let K=(Y.getAttribute("data-reactive-filter-text")??Y.textContent??"").toLowerCase(),U=Y.hasAttribute?.("data-reactive-tags-selected")||j!==""&&!K.includes(j);if(Y.hidden=U,U)Y.removeAttribute("data-reactive-highlighted");else z++}let q=this.element.getAttribute("data-reactive-filter-group");if(q)for(let Y of this.element.querySelectorAll(q)){if(!$(Y))continue;let K=[...Y.querySelectorAll(Z)].filter($);if(K.length===0)continue;Y.hidden=K.every((U)=>U.hidden)}let G=this.element.getAttribute("data-reactive-filter-empty");if(G){for(let Y of this.element.querySelectorAll(G))if($(Y))Y.hidden=z>0}}#HZ(){if(!this.#Q)return;this.element.removeEventListener?.("input",this.#Q),this.element.removeEventListener?.("turbo:morph-element",this.#Q),this.#Q=void 0}#I(){return!!this.element.getAttribute?.("data-reactive-tags-field")}#UZ(){if(typeof this.element?.querySelector!=="function")return!1;return!!this.element.querySelector("[data-reactive-nested-json]")}#g(){if(typeof this.element?.querySelectorAll!=="function")return null;let X=this.element.getAttribute("data-reactive-tags-field");if(!X)return null;let Z=this.#X();return[...this.element.querySelectorAll(X)].find(Z)??null}#c(X){let Z=new Set,$=[];for(let Q of String(X.value??"").split(",")){let j=Q.trim();if(j===""||Z.has(j.toLowerCase()))continue;Z.add(j.toLowerCase()),$.push(j)}return $}#_X(X){let Z=this.#g();if(!Z)return!1;let $=this.#c(Z),Q=new Set($.map((z)=>z.toLowerCase())),j=!1;for(let z of X){let q=String(z??"").trim();if(q===""||Q.has(q.toLowerCase()))continue;Q.add(q.toLowerCase()),$.push(q),j=!0}if(j)this.#MX(Z,$);return j}#MX(X,Z){if(X.value=Z.join(","),typeof X.dispatchEvent==="function")X.dispatchEvent(new Event("input",{bubbles:!0}));this.#d()}#WZ(){if(typeof this.element?.querySelectorAll!=="function")return null;let X=this.element.getAttribute("data-reactive-filter-input");if(!X)return null;let Z=this.#X();return[...this.element.querySelectorAll(X)].find(Z)??null}#d(){let X=this.#g();if(!X)return;let Z=this.#c(X),$=this.#X();this.#JZ(Z,$),this.#LZ(Z,$)}#JZ(X,Z){let $=[...this.element.querySelectorAll("[data-reactive-tags-list]")].find(Z);if(!$)return;let j=[...this.element.querySelectorAll("[data-reactive-tags-template]")].find(Z)?.content?.firstElementChild;if(!j){if(!this.#r)console.warn("[phlex-reactive] reactive_tags: no chip <template data-reactive-tags-template> found in this root — "+"chips will not render (the hidden field still updates). Add a template with a [data-reactive-tag-text] node and a reactive_tags_remove button."),this.#r=!0;return}while($.firstChild)$.removeChild($.firstChild);for(let z of X){let q=j.cloneNode(!0);q.setAttribute?.("data-reactive-tag",z);let G=q.matches?.("[data-reactive-tag-text]")?q:(q.querySelectorAll?.("[data-reactive-tag-text]")??[])[0];if(G)G.textContent=z;let Y=[...q.querySelectorAll?.('[data-action*="reactive#tagsRemove"]')??[]];if(q.matches?.('[data-action*="reactive#tagsRemove"]'))Y.push(q);for(let K of Y)K.setAttribute?.("data-reactive-tag-param",z);$.appendChild(q)}}#LZ(X,Z){let $=new Set(X.map((Q)=>Q.toLowerCase()));for(let Q of this.element.querySelectorAll("[role=option]")){if(!Z(Q))continue;let j=Q.getAttribute?.("data-reactive-tag-param");if(!j)continue;if($.has(j.toLowerCase()))Q.setAttribute("data-reactive-tags-selected","true"),Q.hidden=!0,Q.removeAttribute?.("data-reactive-highlighted");else if(Q.hasAttribute?.("data-reactive-tags-selected")){if(Q.removeAttribute("data-reactive-tags-selected"),!this.#C())Q.hidden=!1}}if(this.#C())this.#A()}#VZ(){return this.#y=Math.max(this.#y+1,Date.now()),this.#y}#BZ(X,Z){let $=[X,...X.querySelectorAll?.("*")??[]];for(let Q of $)for(let j of["name","id","for"]){let z=Q.getAttribute?.(j);if(z&&z.includes("NEW_ROW"))Q.setAttribute?.(j,z.replaceAll("NEW_ROW",String(Z)))}}#_Z(X){if(this.#t)return;console.warn(`[phlex-reactive] nested rows: no owned [data-reactive-nested-list="${X}"] container + <template data-reactive-nested-template="${X}"> pair found in this root — the add `+"trigger did nothing. Render both inside the same reactive root (reactive_nested_list / reactive_nested_template)."),this.#t=!0}#MZ(){if(!this.#B)return;this.element.removeEventListener?.("turbo:morph-element",this.#B),this.#B=void 0}#AZ(){if(this.#j)this.element.removeEventListener?.("input",this.#j),this.element.removeEventListener?.("change",this.#j),this.#j=void 0;if(this.#_)this.element.removeEventListener?.("turbo:morph-element",this.#_),this.#_=void 0}#xZ(){if(!this.#M)return;this.element.removeEventListener?.("turbo:morph-element",this.#M),this.#M=void 0}#PZ(X,Z,$,Q){let j=new FormData;j.append("token",X),j.append("act",Z);for(let[q,G]of Object.entries($))this.#l(j,`params[${q}]`,G);let z=this.#NZ(Q);for(let{name:q,file:G,multiple:Y}of Q){let U=Y||z.has(q)?`params[${q}][]`:`params[${q}]`;j.append(U,G,G.name)}return j}#l(X,Z,$){if($==null)X.append(Z,"");else if(Array.isArray($))$.forEach((Q,j)=>this.#l(X,`${Z}[${j}]`,Q));else if(typeof $==="object")for(let[Q,j]of Object.entries($))this.#l(X,`${Z}[${Q}]`,j);else X.append(Z,String($))}#NZ(X){let Z=new Map;for(let{name:$}of X)Z.set($,(Z.get($)??0)+1);return new Set([...Z].filter(([,$])=>$>1).map(([$])=>$))}#AX(X){if(!X)return{};try{return typeof X==="string"?JSON.parse(X):X}catch{return{}}}#xX(X){return UX(X)}#PX(X){N(X,(Z)=>this.#T(Z))}#T(X){let Z=X.to;if(Z==="@root")return[this.element];if(typeof Z!=="string"||Z==="")return[];if(X.global)return[...document.querySelectorAll(Z)];return[...this.element.querySelectorAll(Z)].filter(($)=>this.#$($))}#OZ(X,Z){if(X?.checked!=="keep")return!1;let $=Z?.type;return $==="checkbox"||$==="radio"}#DZ(X,Z){if(!X)return null;let $=this.#NX(X,Z,!0);return $.length?$:null}#K(X){if(!X)return;if(!this.element.isConnected)return;for(let Z of X)Z()}#RZ(X,Z,$){this.#CZ(X,Z);let Q=$?this.#NX($,Z,!1):[];jX();let j=!1;return()=>{if(j)return;j=!0,this.#IZ(X,Z),zX();for(let z of Q)z()}}#NX(X,Z,$){let Q=[];for(let j of this.#OX(X,Z)){if(X.add_class){let z=X.add_class.filter((q)=>!j.classList.contains(q));if(j.classList.add(...z),z.length)Q.push(()=>j.classList.remove(...z))}if(X.remove_class){let z=X.remove_class.filter((q)=>j.classList.contains(q));if(j.classList.remove(...z),z.length)Q.push(()=>j.classList.add(...z))}if(X.toggle_class)X.toggle_class.forEach((z)=>j.classList.toggle(z)),Q.push(()=>X.toggle_class.forEach((z)=>j.classList.toggle(z)));if(X.hide)j.hidden=!0,Q.push(()=>j.hidden=!1);if(X.show)j.hidden=!1,Q.push(()=>j.hidden=!0)}if(Z&&(X.disable||X.text!=null))Q.push(this.#FZ(X,Z));if($&&X.checked==="keep"&&Z&&"checked"in Z){let j=Z.checked;Q.push(()=>Z.checked=!j)}return Q}#OX(X,Z){if(X.to==null)return Z?[Z]:[];return this.#T({to:X.to})}#FZ(X,Z){let $=this.#P.get(Z);if($)$.count++;else this.#P.set(Z,{count:1,disabled:Z.disabled,html:Z.innerHTML,hadText:X.text!=null,swappedTo:X.text});if(X.disable)Z.disabled=!0;if(X.text!=null)Z.innerHTML=X.text;return()=>this.#TZ(Z,X)}#CZ(X,Z){if(this.#H(Z,X,1),this.#H(this.element,X,1),this.#W.set(X,(this.#W.get(X)??0)+1),this.#k++===0)this.element.setAttribute("aria-busy","true");for(let $ of this.#DX(X))this.#H($,X,1)}#IZ(X,Z){this.#H(Z,X,-1),this.#H(this.element,X,-1);let $=(this.#W.get(X)??1)-1;if($<=0)this.#W.delete(X);else this.#W.set(X,$);if(--this.#k<=0)this.#k=0,this.element.removeAttribute("aria-busy");for(let Q of this.#DX(X))this.#H(Q,X,-1)}#H(X,Z,$){if(!X||typeof X.getAttribute!=="function")return;let Q=this.#S.get(X)??new Map,j=(Q.get(Z)??0)+$;if(j<=0)Q.delete(Z);else Q.set(Z,j);if(Q.size===0){this.#S.delete(X),X.removeAttribute("data-reactive-busy");return}this.#S.set(X,Q),X.setAttribute("data-reactive-busy",[...Q.keys()].join(" "))}#DX(X){return[...this.element.querySelectorAll?.("[data-reactive-busy-on]")??[]].filter(($)=>$.getAttribute("data-reactive-busy-on")===X&&this.#$($))}#TZ(X,Z){let $=this.#P.get(X);if(!$)return;if(--$.count>0)return;if(this.#P.delete(X),!X.isConnected)return;if(Z.disable)X.disabled=$.disabled;if($.hadText&&X.innerHTML===$.swappedTo)X.innerHTML=$.html}#EZ(X){if(!X||typeof X!=="object")return null;let{class:Z,...$}=X;return Z==null?X:{...$,add_class:Z}}#kZ(){return this.#RX??=document.querySelector('meta[name="phlex-reactive-action-path"]')?.content||"/reactive/actions"}#SZ(){if(this.#E!=null)return this.#E;let X=document.querySelector('meta[name="phlex-reactive-timeout"]')?.content,Z=Number(X);return this.#E=Number.isFinite(Z)&&Z>0?Z:30000}#bZ(){return document.querySelector('meta[name="csrf-token"]')?.content??""}#wZ(){return document.querySelector("pgbus-stream-source[connection-id]")?.getAttribute("connection-id")||document.querySelector('meta[name="pgbus-connection-id"]')?.content||null}}export{UZ as resetReactiveDefers,MZ as resetReactiveActivity,VX as registerReactiveVisit,BX as registerReactiveToken,fX as registerReactiveOffline,_X as registerReactiveJs,IX as registerReactiveEffects,RX as registerReactiveDismiss,MX as registerReactiveDefer,l as registerReactiveActions,_Z as reactiveActivityCount,WZ as pendingDeferVia,zX as exitReactiveActivity,gX as escapeRegExp,jX as enterReactiveActivity,pX as enableLatencySim,uX as disableLatencySim,PZ as default,cX as checkReactiveRegistration,AZ as __resetReactiveRegistrationForTest,VZ as __resetReactiveOfflineForTest,BZ as __resetReactiveLatencyForTest,LZ as __resetReactiveEffectsForTest,JZ as __resetReactiveDismissForTest,xZ as __markReactiveConnectedForTest,m as LATENCY_KEY,QX as ACTIVE_ATTR};
2
2
 
3
- //# debugId=3D36FCC8A2F9938464756E2164756E21
3
+ //# debugId=B44B95BDB680993364756E2164756E21
4
4
  //# sourceMappingURL=reactive_controller.min.js.map