phlex-reactive 0.11.3 → 0.11.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +77 -0
- data/README.md +207 -4
- data/app/controllers/phlex/reactive/actions_controller.rb +43 -0
- data/app/javascript/phlex/reactive/reactive_controller.js +362 -1
- data/app/javascript/phlex/reactive/reactive_controller.min.js +2 -2
- data/app/javascript/phlex/reactive/reactive_controller.min.js.map +3 -3
- data/lib/generators/phlex/reactive/install/templates/phlex_reactive.rb.erb +20 -0
- data/lib/phlex/reactive/apm/adapter.rb +43 -0
- data/lib/phlex/reactive/apm/appsignal.rb +52 -0
- data/lib/phlex/reactive/apm/datadog.rb +44 -0
- data/lib/phlex/reactive/apm/sentry.rb +40 -0
- data/lib/phlex/reactive/apm/subscriber.rb +61 -0
- data/lib/phlex/reactive/apm.rb +91 -0
- data/lib/phlex/reactive/component/dsl.rb +27 -4
- data/lib/phlex/reactive/component/helpers.rb +218 -0
- data/lib/phlex/reactive/engine.rb +6 -0
- data/lib/phlex/reactive/version.rb +1 -1
- data/lib/phlex/reactive.rb +89 -1
- metadata +7 -1
|
@@ -1131,6 +1131,17 @@ export default class extends Controller {
|
|
|
1131
1131
|
// Option filtering (issue #163): the ONE delegated sync handler shared by the
|
|
1132
1132
|
// root's input/turbo:morph-element listeners, held for teardown.
|
|
1133
1133
|
#boundSyncFilter
|
|
1134
|
+
// Tag-chip input (issue #203): the bound re-projection attached to
|
|
1135
|
+
// turbo:morph-element (a morph rewrites the hidden field to server truth, so
|
|
1136
|
+
// the chip projection must follow), held for teardown — plus the once-only
|
|
1137
|
+
// missing-template warning latch.
|
|
1138
|
+
#boundSyncTags
|
|
1139
|
+
#tagsWarnedTemplate = false
|
|
1140
|
+
// Draft nested-attribute rows (issue #208): the strictly-monotonic index
|
|
1141
|
+
// counter (clock-seeded so it never collides with server-rendered 0..n
|
|
1142
|
+
// indexes) plus the once-only missing-list/template warning latch.
|
|
1143
|
+
#nestedIndex = 0
|
|
1144
|
+
#nestedWarned = false
|
|
1134
1145
|
// Connect-time compute seed (issue #199): the bound re-seed attached to
|
|
1135
1146
|
// turbo:morph-element so an in-place morph re-runs the compute, held for teardown.
|
|
1136
1147
|
#boundSeedCompute
|
|
@@ -1240,6 +1251,21 @@ export default class extends Controller {
|
|
|
1240
1251
|
this.#syncFilter()
|
|
1241
1252
|
}
|
|
1242
1253
|
|
|
1254
|
+
// Tag-chip input (issue #203) — ONLY when the root names the hidden value
|
|
1255
|
+
// field (reactive_tags), so a component without one pays one attribute
|
|
1256
|
+
// read. The chip list is a CLIENT PROJECTION of the hidden field's
|
|
1257
|
+
// comma-joined value: connect seeds it (a plain replace re-connects with
|
|
1258
|
+
// the server-rendered value), and turbo:morph-element re-projects after an
|
|
1259
|
+
// in-place morph (the morph wrote server truth into the hidden field while
|
|
1260
|
+
// the chips DOM kept the pre-morph projection). Registered AFTER the
|
|
1261
|
+
// filter's listeners so a morph re-filters first and the tags pass then
|
|
1262
|
+
// re-marks selected options on the fresh visibility state.
|
|
1263
|
+
if (this.#tagsEnabled()) {
|
|
1264
|
+
this.#boundSyncTags = () => this.#syncTags()
|
|
1265
|
+
this.element.addEventListener?.("turbo:morph-element", this.#boundSyncTags)
|
|
1266
|
+
this.#syncTags()
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1243
1269
|
// Connect-time compute seed (issue #199) — ONLY when the root carries a
|
|
1244
1270
|
// reactive_compute binding that opts in (data-reactive-compute-seed). A
|
|
1245
1271
|
// freshly-rendered compute root (a first paint, or a server validation-error
|
|
@@ -1291,6 +1317,7 @@ export default class extends Controller {
|
|
|
1291
1317
|
this.#teardownDirtyTracking()
|
|
1292
1318
|
this.#teardownShowSync()
|
|
1293
1319
|
this.#teardownFilterSync()
|
|
1320
|
+
this.#teardownTagsSync()
|
|
1294
1321
|
this.#teardownComputeSeed()
|
|
1295
1322
|
if (this.#boundProbeLazyDefer) {
|
|
1296
1323
|
this.element.removeEventListener?.("turbo:morph-element", this.#boundProbeLazyDefer)
|
|
@@ -1697,6 +1724,141 @@ export default class extends Controller {
|
|
|
1697
1724
|
return Array.from(this.element.querySelectorAll(selector)).filter((el) => !el.hidden && owns(el))
|
|
1698
1725
|
}
|
|
1699
1726
|
|
|
1727
|
+
// Tag-chip input (issue #203) — the composed combobox/tags primitive. The
|
|
1728
|
+
// root's data-reactive-tags-field names the hidden input that stores the
|
|
1729
|
+
// COMMA-JOINED value; these three actions are its only writers. All of it is
|
|
1730
|
+
// FORM state (like text in an input) — no token, no POST: the surrounding
|
|
1731
|
+
// form submit carries the joined value. The chip list is re-projected from
|
|
1732
|
+
// the field on every write (#syncTags), so the field stays the single source
|
|
1733
|
+
// of truth.
|
|
1734
|
+
//
|
|
1735
|
+
// Enter on the query input: add the TYPED text — unless this Enter belongs
|
|
1736
|
+
// to listnav (reactive_tags_add composes after reactive_listnav on the same
|
|
1737
|
+
// keydown.enter). Two guards make the composition order-independent:
|
|
1738
|
+
// defaultPrevented means listnavPick ALREADY picked the highlighted option
|
|
1739
|
+
// (adding the typed text too would double-add); a still-visible highlighted
|
|
1740
|
+
// option means listnavPick is ABOUT to pick it (when tagsAdd is bound
|
|
1741
|
+
// first). Past the guards, Enter is OURS — preventDefault unconditionally so
|
|
1742
|
+
// it can never submit the enclosing form (blank input included). A
|
|
1743
|
+
// comma-separated paste splits into individual tags (the value is
|
|
1744
|
+
// comma-joined, so a comma can never be part of one tag). The input clears
|
|
1745
|
+
// only when something was actually added — a duplicate keeps the typed text
|
|
1746
|
+
// for correction.
|
|
1747
|
+
tagsAdd(event) {
|
|
1748
|
+
if (!this.#tagsEnabled()) return
|
|
1749
|
+
if (event?.defaultPrevented) return
|
|
1750
|
+
if (this.#listnavOptions(event).some((el) => el.hasAttribute?.("data-reactive-highlighted"))) return
|
|
1751
|
+
event?.preventDefault?.()
|
|
1752
|
+
|
|
1753
|
+
const input = event?.currentTarget ?? event?.target
|
|
1754
|
+
if (!input) return
|
|
1755
|
+
const added = this.#tagsAddValues(String(input.value ?? "").split(","))
|
|
1756
|
+
if (!added) return
|
|
1757
|
+
input.value = ""
|
|
1758
|
+
if (this.#filterEnabled()) this.#syncFilter()
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
// Click (or listnav Enter, which CLICKS the highlighted option) on a
|
|
1762
|
+
// preloaded option: add its DECLARED tag (data-reactive-tag-param — set by
|
|
1763
|
+
// reactive_tags_option, never free text). After a successful add, reset the
|
|
1764
|
+
// query so the next tag starts from the full list: clear the filter input,
|
|
1765
|
+
// re-narrow, and hand focus back for continued typing.
|
|
1766
|
+
tagsPick(event) {
|
|
1767
|
+
if (!this.#tagsEnabled()) return
|
|
1768
|
+
event?.preventDefault?.()
|
|
1769
|
+
|
|
1770
|
+
const trigger = event?.currentTarget ?? event?.target
|
|
1771
|
+
const tag = trigger?.getAttribute?.("data-reactive-tag-param")
|
|
1772
|
+
if (!tag) return
|
|
1773
|
+
if (!this.#tagsAddValues([tag])) return
|
|
1774
|
+
|
|
1775
|
+
const input = this.#tagsQueryInput()
|
|
1776
|
+
if (!input) return
|
|
1777
|
+
input.value = ""
|
|
1778
|
+
this.#syncFilter()
|
|
1779
|
+
input.focus?.()
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
// Click on a chip's remove button: drop its tag (case-insensitive match, the
|
|
1783
|
+
// dedupe convention) from the hidden value. The re-projection removes the
|
|
1784
|
+
// chip and resurfaces the option. Removing an absent tag is a no-op.
|
|
1785
|
+
tagsRemove(event) {
|
|
1786
|
+
if (!this.#tagsEnabled()) return
|
|
1787
|
+
event?.preventDefault?.()
|
|
1788
|
+
|
|
1789
|
+
const trigger = event?.currentTarget ?? event?.target
|
|
1790
|
+
const tag = trigger?.getAttribute?.("data-reactive-tag-param")
|
|
1791
|
+
if (!tag) return
|
|
1792
|
+
const field = this.#tagsField()
|
|
1793
|
+
if (!field) return
|
|
1794
|
+
|
|
1795
|
+
const tags = this.#tagsRead(field)
|
|
1796
|
+
const next = tags.filter((t) => t.toLowerCase() !== tag.toLowerCase())
|
|
1797
|
+
if (next.length === tags.length) return
|
|
1798
|
+
this.#tagsWrite(field, next)
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
// Draft nested-attribute rows (issue #208) — the "new parent + child rows"
|
|
1802
|
+
// window. The rows are FORM state (the reactive_tags posture): no token, no
|
|
1803
|
+
// POST, ever — the surrounding REAL form submit carries Rails'
|
|
1804
|
+
// accepts_nested_attributes_for names and the server reconciles parent +
|
|
1805
|
+
// rows in ONE create. Add clones the association's server-owned
|
|
1806
|
+
// <template data-reactive-nested-template="assoc"> row, swaps every NEW_ROW
|
|
1807
|
+
// in the clone's name/id/for for a fresh unique index (each row posts as its
|
|
1808
|
+
// own `…_attributes[<index>][field]` group), appends it to the owned
|
|
1809
|
+
// [data-reactive-nested-list="assoc"] container, and focuses the new row's
|
|
1810
|
+
// first field. Several collections can share one root — everything is keyed
|
|
1811
|
+
// by the association name the trigger carries.
|
|
1812
|
+
nestedAdd(event) {
|
|
1813
|
+
event?.preventDefault?.()
|
|
1814
|
+
const trigger = event?.currentTarget ?? event?.target
|
|
1815
|
+
const assoc = trigger?.getAttribute?.("data-reactive-association-param")
|
|
1816
|
+
if (!assoc) return
|
|
1817
|
+
if (typeof this.element?.querySelectorAll !== "function") return
|
|
1818
|
+
|
|
1819
|
+
const owns = this.#ownershipFilter()
|
|
1820
|
+
const list = [...this.element.querySelectorAll(`[data-reactive-nested-list="${assoc}"]`)].find(owns)
|
|
1821
|
+
const template = [...this.element.querySelectorAll(`[data-reactive-nested-template="${assoc}"]`)].find(owns)
|
|
1822
|
+
const proto = template?.content?.firstElementChild
|
|
1823
|
+
if (!list || !proto) {
|
|
1824
|
+
this.#warnNestedOnce(assoc)
|
|
1825
|
+
return
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
const row = proto.cloneNode(true)
|
|
1829
|
+
this.#renumberNestedRow(row, this.#nextNestedIndex())
|
|
1830
|
+
list.appendChild(row)
|
|
1831
|
+
const first = [...(row.querySelectorAll?.("input, select, textarea") ?? [])][0]
|
|
1832
|
+
first?.focus?.()
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
// Remove the trigger's closest row wrapper. A DRAFT row (no [_destroy]
|
|
1836
|
+
// input) leaves the DOM — it was never persisted, so removing its fields IS
|
|
1837
|
+
// the removal. A PERSISTED row (an edit form rendered a hidden [_destroy]
|
|
1838
|
+
// input via nested_field_name) is marked "1" and hidden instead — Rails
|
|
1839
|
+
// destroys it on save. The mark dispatches a real bubbling `input` (the
|
|
1840
|
+
// set-value + dispatch contract, issue #183) so dirty tracking/compute see it.
|
|
1841
|
+
nestedRemove(event) {
|
|
1842
|
+
event?.preventDefault?.()
|
|
1843
|
+
const trigger = event?.currentTarget ?? event?.target
|
|
1844
|
+
const row = trigger?.closest?.("[data-reactive-nested-row]")
|
|
1845
|
+
if (!row) return
|
|
1846
|
+
// The closest() walk must not escape this root — a root can itself sit
|
|
1847
|
+
// inside ANOTHER collection's row (the issue #15 closest-form posture).
|
|
1848
|
+
if (row.closest?.('[data-controller~="reactive"]') !== this.element) return
|
|
1849
|
+
|
|
1850
|
+
const destroy = [...(row.querySelectorAll?.('input[name$="[_destroy]"]') ?? [])][0]
|
|
1851
|
+
if (destroy) {
|
|
1852
|
+
destroy.value = "1"
|
|
1853
|
+
if (typeof destroy.dispatchEvent === "function") {
|
|
1854
|
+
destroy.dispatchEvent(new Event("input", { bubbles: true }))
|
|
1855
|
+
}
|
|
1856
|
+
row.hidden = true
|
|
1857
|
+
} else {
|
|
1858
|
+
row.parentNode?.removeChild?.(row)
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1700
1862
|
// Parse a JSON string list from a root data attr; [] on absence/parse error so
|
|
1701
1863
|
// a malformed binding degrades to "no fields" rather than throwing on input.
|
|
1702
1864
|
#parseComputeList(attr) {
|
|
@@ -2891,7 +3053,11 @@ export default class extends Controller {
|
|
|
2891
3053
|
for (const el of this.element.querySelectorAll(optionSelector)) {
|
|
2892
3054
|
if (!owns(el)) continue // a nested root's option is its own controller's job
|
|
2893
3055
|
const haystack = (el.getAttribute("data-reactive-filter-text") ?? el.textContent ?? "").toLowerCase()
|
|
2894
|
-
|
|
3056
|
+
// An option whose tag is already selected (reactive_tags, issue #203)
|
|
3057
|
+
// stays hidden through every re-filter — clearing the query must not
|
|
3058
|
+
// resurface an already-added tag.
|
|
3059
|
+
const hidden =
|
|
3060
|
+
el.hasAttribute?.("data-reactive-tags-selected") || (query !== "" && !haystack.includes(query))
|
|
2895
3061
|
el.hidden = hidden
|
|
2896
3062
|
if (hidden) el.removeAttribute("data-reactive-highlighted")
|
|
2897
3063
|
else visible++
|
|
@@ -2926,6 +3092,201 @@ export default class extends Controller {
|
|
|
2926
3092
|
this.#boundSyncFilter = undefined
|
|
2927
3093
|
}
|
|
2928
3094
|
|
|
3095
|
+
// Whether this root declares a tag-chip binding (issue #203) — the connect()
|
|
3096
|
+
// gate and every tags action's first check (an action bound without the root
|
|
3097
|
+
// binding is default-denied, the filter posture).
|
|
3098
|
+
#tagsEnabled() {
|
|
3099
|
+
return !!this.element.getAttribute?.("data-reactive-tags-field")
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
// The hidden input storing the comma-joined value, resolved fresh per use
|
|
3103
|
+
// (a morph replaces nodes — never cache it) and OWNED by this root (issue
|
|
3104
|
+
// #15). null when the selector resolves nothing — every caller then no-ops:
|
|
3105
|
+
// a binding that can't resolve must never break the page.
|
|
3106
|
+
#tagsField() {
|
|
3107
|
+
if (typeof this.element?.querySelectorAll !== "function") return null
|
|
3108
|
+
const selector = this.element.getAttribute("data-reactive-tags-field")
|
|
3109
|
+
if (!selector) return null
|
|
3110
|
+
const owns = this.#ownershipFilter()
|
|
3111
|
+
return [...this.element.querySelectorAll(selector)].find(owns) ?? null
|
|
3112
|
+
}
|
|
3113
|
+
|
|
3114
|
+
// Parse the field's comma-joined value into the canonical tag list: split,
|
|
3115
|
+
// trim, drop blanks, dedupe case-insensitively KEEPING the first casing (the
|
|
3116
|
+
// server may have stored a ragged value — the projection normalizes without
|
|
3117
|
+
// rewriting the field, so we never fight server truth).
|
|
3118
|
+
#tagsRead(field) {
|
|
3119
|
+
const seen = new Set()
|
|
3120
|
+
const tags = []
|
|
3121
|
+
for (const part of String(field.value ?? "").split(",")) {
|
|
3122
|
+
const tag = part.trim()
|
|
3123
|
+
if (tag === "" || seen.has(tag.toLowerCase())) continue
|
|
3124
|
+
seen.add(tag.toLowerCase())
|
|
3125
|
+
tags.push(tag)
|
|
3126
|
+
}
|
|
3127
|
+
return tags
|
|
3128
|
+
}
|
|
3129
|
+
|
|
3130
|
+
// Append any NEW tags (trimmed, non-blank, not already present under the
|
|
3131
|
+
// case-insensitive dedupe) and write the field once. Returns whether
|
|
3132
|
+
// anything was actually added — callers only clear the query input then.
|
|
3133
|
+
#tagsAddValues(values) {
|
|
3134
|
+
const field = this.#tagsField()
|
|
3135
|
+
if (!field) return false
|
|
3136
|
+
|
|
3137
|
+
const tags = this.#tagsRead(field)
|
|
3138
|
+
const seen = new Set(tags.map((tag) => tag.toLowerCase()))
|
|
3139
|
+
let added = false
|
|
3140
|
+
for (const value of values) {
|
|
3141
|
+
const tag = String(value ?? "").trim()
|
|
3142
|
+
if (tag === "" || seen.has(tag.toLowerCase())) continue
|
|
3143
|
+
seen.add(tag.toLowerCase())
|
|
3144
|
+
tags.push(tag)
|
|
3145
|
+
added = true
|
|
3146
|
+
}
|
|
3147
|
+
if (added) this.#tagsWrite(field, tags)
|
|
3148
|
+
return added
|
|
3149
|
+
}
|
|
3150
|
+
|
|
3151
|
+
// The ONE writer: join, store, dispatch a real bubbling `input` on the field
|
|
3152
|
+
// (the set-value + dispatch contract, issue #183 — dirty tracking,
|
|
3153
|
+
// reactive_show, and compute all see the change), then re-project.
|
|
3154
|
+
#tagsWrite(field, tags) {
|
|
3155
|
+
field.value = tags.join(",")
|
|
3156
|
+
if (typeof field.dispatchEvent === "function") {
|
|
3157
|
+
field.dispatchEvent(new Event("input", { bubbles: true }))
|
|
3158
|
+
}
|
|
3159
|
+
this.#syncTags()
|
|
3160
|
+
}
|
|
3161
|
+
|
|
3162
|
+
// The query input the tags widget resets after a pick — the SAME input that
|
|
3163
|
+
// drives reactive_filter (a tags widget without filtering has none; the
|
|
3164
|
+
// caller then skips the reset).
|
|
3165
|
+
#tagsQueryInput() {
|
|
3166
|
+
if (typeof this.element?.querySelectorAll !== "function") return null
|
|
3167
|
+
const selector = this.element.getAttribute("data-reactive-filter-input")
|
|
3168
|
+
if (!selector) return null
|
|
3169
|
+
const owns = this.#ownershipFilter()
|
|
3170
|
+
return [...this.element.querySelectorAll(selector)].find(owns) ?? null
|
|
3171
|
+
}
|
|
3172
|
+
|
|
3173
|
+
// Re-project the hidden field into the DOM (issue #203): rebuild the chip
|
|
3174
|
+
// list from the <template> and mark/hide the options whose tag is already
|
|
3175
|
+
// selected. The field is the single source of truth — this never writes it.
|
|
3176
|
+
#syncTags() {
|
|
3177
|
+
const field = this.#tagsField()
|
|
3178
|
+
if (!field) return
|
|
3179
|
+
const tags = this.#tagsRead(field)
|
|
3180
|
+
const owns = this.#ownershipFilter()
|
|
3181
|
+
this.#tagsRenderChips(tags, owns)
|
|
3182
|
+
this.#tagsMarkOptions(tags, owns)
|
|
3183
|
+
}
|
|
3184
|
+
|
|
3185
|
+
// Rebuild the chip list: clear the container and clone one chip per tag from
|
|
3186
|
+
// the server-owned template. The tag lands in the clone's
|
|
3187
|
+
// [data-reactive-tag-text] node via textContent (XSS-safe by construction —
|
|
3188
|
+
// never innerHTML, the reactive_text posture), and every tagsRemove trigger
|
|
3189
|
+
// in the clone gets the tag as its param. A missing list is a chip-less
|
|
3190
|
+
// widget (fine — the value still maintains); a missing/empty template warns
|
|
3191
|
+
// ONCE (a half-built binding should be loud, but never per-keystroke).
|
|
3192
|
+
#tagsRenderChips(tags, owns) {
|
|
3193
|
+
const list = [...this.element.querySelectorAll("[data-reactive-tags-list]")].find(owns)
|
|
3194
|
+
if (!list) return
|
|
3195
|
+
|
|
3196
|
+
const template = [...this.element.querySelectorAll("[data-reactive-tags-template]")].find(owns)
|
|
3197
|
+
const chipProto = template?.content?.firstElementChild
|
|
3198
|
+
if (!chipProto) {
|
|
3199
|
+
if (!this.#tagsWarnedTemplate) {
|
|
3200
|
+
console.warn(
|
|
3201
|
+
"[phlex-reactive] reactive_tags: no chip <template data-reactive-tags-template> found in this root — " +
|
|
3202
|
+
"chips will not render (the hidden field still updates). Add a template with a " +
|
|
3203
|
+
"[data-reactive-tag-text] node and a reactive_tags_remove button."
|
|
3204
|
+
)
|
|
3205
|
+
this.#tagsWarnedTemplate = true
|
|
3206
|
+
}
|
|
3207
|
+
return
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3210
|
+
while (list.firstChild) list.removeChild(list.firstChild)
|
|
3211
|
+
for (const tag of tags) {
|
|
3212
|
+
const chip = chipProto.cloneNode(true)
|
|
3213
|
+
chip.setAttribute?.("data-reactive-tag", tag)
|
|
3214
|
+
const sink = chip.matches?.("[data-reactive-tag-text]")
|
|
3215
|
+
? chip
|
|
3216
|
+
: (chip.querySelectorAll?.("[data-reactive-tag-text]") ?? [])[0]
|
|
3217
|
+
if (sink) sink.textContent = tag
|
|
3218
|
+
const removers = [...(chip.querySelectorAll?.('[data-action*="reactive#tagsRemove"]') ?? [])]
|
|
3219
|
+
if (chip.matches?.('[data-action*="reactive#tagsRemove"]')) removers.push(chip)
|
|
3220
|
+
for (const remover of removers) remover.setAttribute?.("data-reactive-tag-param", tag)
|
|
3221
|
+
list.appendChild(chip)
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
|
|
3225
|
+
// Hide + mark every owned option whose DECLARED tag is already selected
|
|
3226
|
+
// (data-reactive-tags-selected — #syncFilter keeps it hidden through
|
|
3227
|
+
// re-filters), and resurface an option WE hid when its tag is removed. Only
|
|
3228
|
+
// marker-carrying options are un-hidden — an option hidden by the filter or
|
|
3229
|
+
// the server stays as-is. With a filter bound, one final #syncFilter re-folds
|
|
3230
|
+
// groups/empty against the new selected set.
|
|
3231
|
+
#tagsMarkOptions(tags, owns) {
|
|
3232
|
+
const selected = new Set(tags.map((tag) => tag.toLowerCase()))
|
|
3233
|
+
for (const el of this.element.querySelectorAll("[role=option]")) {
|
|
3234
|
+
if (!owns(el)) continue
|
|
3235
|
+
const tag = el.getAttribute?.("data-reactive-tag-param")
|
|
3236
|
+
if (!tag) continue
|
|
3237
|
+
if (selected.has(tag.toLowerCase())) {
|
|
3238
|
+
el.setAttribute("data-reactive-tags-selected", "true")
|
|
3239
|
+
el.hidden = true
|
|
3240
|
+
el.removeAttribute?.("data-reactive-highlighted")
|
|
3241
|
+
} else if (el.hasAttribute?.("data-reactive-tags-selected")) {
|
|
3242
|
+
el.removeAttribute("data-reactive-tags-selected")
|
|
3243
|
+
if (!this.#filterEnabled()) el.hidden = false
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
if (this.#filterEnabled()) this.#syncFilter()
|
|
3247
|
+
}
|
|
3248
|
+
|
|
3249
|
+
// A fresh index per nested-row add (issue #208) — strictly monotonic and
|
|
3250
|
+
// clock-seeded, so it can never collide with server-rendered integer indexes
|
|
3251
|
+
// (0..n) NOR with a rapid same-millisecond double add.
|
|
3252
|
+
#nextNestedIndex() {
|
|
3253
|
+
this.#nestedIndex = Math.max(this.#nestedIndex + 1, Date.now())
|
|
3254
|
+
return this.#nestedIndex
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3257
|
+
// Swap every NEW_ROW in the clone's name/id/for for the fresh index, so the
|
|
3258
|
+
// row posts as its own `…_attributes[<index>][field]` group and labels keep
|
|
3259
|
+
// pointing at their (renumbered) inputs.
|
|
3260
|
+
#renumberNestedRow(row, index) {
|
|
3261
|
+
const nodes = [row, ...(row.querySelectorAll?.("*") ?? [])]
|
|
3262
|
+
for (const el of nodes) {
|
|
3263
|
+
for (const attr of ["name", "id", "for"]) {
|
|
3264
|
+
const value = el.getAttribute?.(attr)
|
|
3265
|
+
if (value && value.includes("NEW_ROW")) el.setAttribute?.(attr, value.replaceAll("NEW_ROW", String(index)))
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
|
|
3270
|
+
// A half-built nested-rows binding should be loud, but never per-click.
|
|
3271
|
+
#warnNestedOnce(assoc) {
|
|
3272
|
+
if (this.#nestedWarned) return
|
|
3273
|
+
console.warn(
|
|
3274
|
+
`[phlex-reactive] nested rows: no owned [data-reactive-nested-list="${assoc}"] container + ` +
|
|
3275
|
+
`<template data-reactive-nested-template="${assoc}"> pair found in this root — the add ` +
|
|
3276
|
+
"trigger did nothing. Render both inside the same reactive root (reactive_nested_list / " +
|
|
3277
|
+
"reactive_nested_template)."
|
|
3278
|
+
)
|
|
3279
|
+
this.#nestedWarned = true
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
// Remove the tags morph listener on disconnect, so a stray morph event after
|
|
3283
|
+
// the element leaves the DOM never re-projects against a detached root.
|
|
3284
|
+
#teardownTagsSync() {
|
|
3285
|
+
if (!this.#boundSyncTags) return
|
|
3286
|
+
this.element.removeEventListener?.("turbo:morph-element", this.#boundSyncTags)
|
|
3287
|
+
this.#boundSyncTags = undefined
|
|
3288
|
+
}
|
|
3289
|
+
|
|
2929
3290
|
// Remove the compute seed morph listener on disconnect (issue #199), so a
|
|
2930
3291
|
// stray turbo:morph-element after the element leaves the DOM never re-seeds
|
|
2931
3292
|
// against a detached root.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{Controller as Qj}from"@hotwired/stimulus";import{confirmResolver as b}from"phlex/reactive/confirm";import{computeReducer as Xj}from"phlex/reactive/compute";import{confirmPredicate as Zj}from"phlex/reactive/confirm_predicate";function $j(){let j=window.Turbo?.StreamActions;if(!j||j["reactive:visit"])return;j["reactive:visit"]=function(){let Q=this.getAttribute("data-url");if(Q)window.Turbo.visit(Q,{action:"advance"})}}function Jj(){let j=window.Turbo?.StreamActions;if(!j||j["reactive:token"])return;j["reactive:token"]=function(){let Q=this.getAttribute("data-reactive-token-value"),X=this.getAttribute("target");if(!Q||!X)return;let Z=document.getElementById(X);if(Z)Z.setAttribute("data-reactive-token-value",Q)}}function Gj(){let j=window.Turbo?.StreamActions;if(!j||j["reactive:js"])return;j["reactive:js"]=function(){let Q=e(this.getAttribute("data-reactive-ops"));if(!Q.length)return;let X=this.getAttribute("target"),Z=X?document.getElementById(X):null;if(X&&!Z)return;jj(Q,($)=>wj($,Z))}}var V=new Map;function p(j,Q){let X=!V.has(j);if(V.set(j,Q),X)l()}function O(j){if(V.delete(j))o()}function fj(){V.clear(),D=!1}function pj(j){return V.get(j)?.via}var D=!1;function zj(){let j=window.Turbo?.StreamActions;if(!j||j["reactive:defer"])return;if(j["reactive:defer"]=function(){let Q=this.getAttribute("target");if(!Q)return;if(this.getAttribute("data-reactive-defer-via")==="stream"){qj(Q,this);return}let X=this.getAttribute("data-reactive-defer-token");if(!X)return;P(Q,X)},!D&&typeof document<"u"&&document.addEventListener)D=!0,document.addEventListener("turbo:before-stream-render",Kj)}function Kj(j){let X=j.target?.getAttribute?.("target");if(!X)return;let Z=X.startsWith("reactive-defer-src-")?X.slice(19):X;if(V.get(Z)?.via==="stream")O(Z)}function P(j,Q){let X=document.getElementById(j);if(!X){console.warn(`[phlex-reactive] reactive:defer target #${j} is not on the page — skipped`);return}m(j),g(X);let Z={via:"fetch",abort:new AbortController,timedOut:!1};p(j,Z),Yj(j,Z,Q)}function qj(j,Q){let X=document.getElementById(j);if(!X){console.warn(`[phlex-reactive] reactive:defer target #${j} is not on the page — skipped`);return}let Z=Q.getAttribute("data-reactive-defer-src");if(!Z)return;if(!globalThis.customElements?.get?.("pgbus-stream-source")){let J=Q.getAttribute("data-reactive-defer-token");if(J){P(j,J);return}console.error("[phlex-reactive] reactive:defer via=stream but <pgbus-stream-source> is not registered "+"and no fallback token was provided — is the pgbus client loaded on this page?");return}m(j),g(X);let $=document.createElement("pgbus-stream-source");$.id=u(j),$.setAttribute("src",Z),$.setAttribute("since-id",Q.getAttribute("data-reactive-defer-since-id")??"0"),$.setAttribute("hidden",""),document.body.appendChild($),p(j,{via:"stream"})}function u(j){return`reactive-defer-src-${j}`}async function Yj(j,Q,X){let Z=setTimeout(()=>{Q.timedOut=!0,Q.abort.abort()},Wj()),$;try{$=await fetch(Uj(),{method:"POST",headers:{Accept:"text/vnd.turbo-stream.html","Content-Type":"application/json","X-CSRF-Token":Hj()},body:JSON.stringify({token:X}),credentials:"same-origin",signal:Q.abort.signal})}catch(G){if(clearTimeout(Z),V.get(j)!==Q)return;console.error("[phlex-reactive] deferred render failed",G),F(j,X);return}if(V.get(j)!==Q){clearTimeout(Z);return}if($.status===204){clearTimeout(Z),v(j);return}if(!$.ok){clearTimeout(Z),console.error(`[phlex-reactive] deferred render failed: HTTP ${$.status}`),F(j,X,$.status);return}let J;try{J=await $.text()}catch(G){if(clearTimeout(Z),V.get(j)!==Q)return;console.error("[phlex-reactive] deferred render failed reading the body",G),F(j,X);return}if(clearTimeout(Z),V.get(j)!==Q)return;v(j),window.Turbo.renderStreamMessage(J)}function m(j){let Q=V.get(j);if(!Q)return;if(O(j),Q.via==="fetch")Q.abort.abort();else document.getElementById(u(j))?.remove?.()}function g(j){j.setAttribute("data-reactive-defer-pending","true"),j.setAttribute("aria-busy","true")}function c(j){j.removeAttribute("data-reactive-defer-pending"),j.removeAttribute("aria-busy")}function v(j){O(j);let Q=document.getElementById(j);if(!Q)return;c(Q),Q.removeAttribute("data-reactive-error")}function F(j,Q,X){O(j);let Z=document.getElementById(j);if(!Z)return;c(Z),Z.setAttribute("data-reactive-error","defer");let $=()=>{let J=document.getElementById(j);if(!J){console.warn("[phlex-reactive] defer retry() ignored — the target left the DOM");return}J.removeAttribute("data-reactive-error"),P(j,Q)};Z.dispatchEvent(new CustomEvent("reactive:error",{bubbles:!0,composed:!0,detail:{kind:"defer",target:j,status:X,retry:$}}))}function Uj(){return document.querySelector('meta[name="phlex-reactive-defer-path"]')?.content||"/reactive/defer"}function Hj(){return document.querySelector('meta[name="csrf-token"]')?.content??""}function Wj(){let j=document.querySelector('meta[name="phlex-reactive-timeout"]')?.content,Q=Number(j);return Number.isFinite(Q)&&Q>0?Q:30000}var I=!1;function _j(){if(I)return;if(typeof document>"u"||!document.addEventListener)return;I=!0,document.addEventListener("turbo:before-stream-render",Lj)}function Lj(j){let Q=j.detail,X=Q?.render;if(typeof X!=="function"||X.__reactiveDismissWrapped){if(typeof requestAnimationFrame==="function")requestAnimationFrame(C);else setTimeout(C,0);return}let Z=async($)=>{await X($),C()};Z.__reactiveDismissWrapped=!0,Q.render=Z}function C(){let j=document.querySelectorAll("[data-reactive-dismiss-after]");for(let Q of j){if(Q.hasAttribute("data-reactive-dismiss-scheduled"))continue;let X=Number(Q.getAttribute("data-reactive-dismiss-after"));if(!Number.isFinite(X)||X<=0)continue;Q.setAttribute("data-reactive-dismiss-scheduled",""),setTimeout(()=>Q.remove(),X)}}function uj(){I=!1}var S=!1;function Nj(){if(S)return;if(typeof window>"u"||typeof document>"u")return;if(typeof window.addEventListener!=="function")return;S=!0;let j=()=>{let Q=document.documentElement;if(typeof Q?.toggleAttribute!=="function")return;Q.toggleAttribute("data-reactive-offline",globalThis.navigator?.onLine===!1)};j(),window.addEventListener("online",j),window.addEventListener("offline",j)}function mj(){S=!1}var k="phlex-reactive:latency",x=!1;function Vj(j){if(typeof sessionStorage>"u")return;sessionStorage.setItem(k,String(j))}function Aj(){if(typeof sessionStorage>"u")return;sessionStorage.removeItem(k),x=!1}function Mj(){if(typeof window>"u"||typeof document>"u")return;if(document.querySelector?.('meta[name="phlex-reactive-env"]')?.content!=="development")return;window.PhlexReactive={enableLatencySim:Vj,disableLatencySim:Aj}}function gj(){x=!1}var d="data-reactive-active",A=0;function l(){if(A++,A===1)s("reactive:busy")}function o(){if(A===0)return;if(A--,A===0)s("reactive:idle")}function cj(){return A}function dj(){A=0,(typeof document<"u"?document.documentElement:null)?.removeAttribute?.(d)}function s(j){if(typeof document>"u")return;let Q=document.documentElement;if(typeof Q?.toggleAttribute==="function")Q.toggleAttribute(d,A>0);if(typeof document.dispatchEvent==="function"&&typeof CustomEvent==="function")document.dispatchEvent(new CustomEvent(j,{detail:{count:A}}))}function h(){$j(),Jj(),Gj(),zj(),_j(),Nj(),Mj()}function Bj(j){return j.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}if(typeof window<"u")if(window.Turbo)h();else document.addEventListener("turbo:load",h,{once:!0});var E=!1;function xj(){if(E)return;if(typeof document>"u")return;let j=document.querySelectorAll('[data-controller~="reactive"]');if(!j||j.length===0)return;console.warn("[phlex-reactive] found "+j.length+' element(s) with data-controller="reactive" '+"but the reactive controller never connected. It is loaded but not registered — "+'add `application.register("reactive", ReactiveController)` (importmap) or import it into app/javascript/controllers/ for lazyLoadControllersFrom apps. See the README.')}function lj(){E=!1}function oj(){E=!0}if(typeof window<"u"&&typeof document<"u"){let j=()=>setTimeout(xj,0);if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",j,{once:!0});else j()}var Oj=new Set(["href","src","srcdoc","action","formaction","xlink:href","style"]);function Pj(j){let Q=String(j).toLowerCase();return Q.startsWith("on")||Oj.has(Q)}function Ej(j,Q,X){let[Z,$,J]=Q;j.classList.add(Z,$),X(),requestAnimationFrame(()=>{j.classList.remove($),j.classList.add(J)});let G=!1,K=()=>{if(G)return;G=!0,j.classList.remove(Z,J)};j.addEventListener("animationend",K,{once:!0}),setTimeout(K,350)}var y=Object.freeze({show:(j,Q)=>R(j,!1,Q),hide:(j,Q)=>R(j,!0,Q),toggle:(j,Q)=>R(j,!j.hidden,Q),add_class:(j,Q)=>j.classList.add(...Q.classes??[]),remove_class:(j,Q)=>j.classList.remove(...Q.classes??[]),toggle_class:(j,Q)=>(Q.classes??[]).forEach((X)=>j.classList.toggle(X)),set_attr:(j,Q)=>{if(T(Q.name))j.setAttribute(Q.name,Q.value??"")},remove_attr:(j,Q)=>{if(T(Q.name))j.removeAttribute(Q.name)},toggle_attr:(j,Q)=>{if(!T(Q.name))return;if(j.hasAttribute(Q.name))j.removeAttribute(Q.name);else j.setAttribute(Q.name,"")},focus:(j)=>j.focus?.(),focus_first:(j)=>kj(j)?.focus?.(),text:(j,Q)=>{let X=String(Q.value??"");if(j.textContent!==X)j.textContent=X},dispatch:(j,Q)=>{j.dispatchEvent(new CustomEvent(Q.name,{bubbles:!0,composed:!0,detail:Q.detail??{}}))}});function R(j,Q,X){if(X?.transition)Ej(j,X.transition,()=>j.hidden=Q);else j.hidden=Q}function T(j){if(!Pj(j))return!0;return console.warn(`[phlex-reactive] refused client attr op on ${JSON.stringify(j)} — skipped`),!1}var i=/^#[A-Za-z_][\w-]*$/;function Fj(j){if(typeof j==="string"&&i.test(j))return!0;return console.warn(`[phlex-reactive] refused cross-root mirror target ${JSON.stringify(j)} — skipped`),!1}function Cj(j,Q){let X=j.getAttribute("data-reactive-show-equals");if(X!==null)return Q===X;let Z=j.getAttribute("data-reactive-show-not");if(Z!==null)return Q!==Z;let $=j.getAttribute("data-reactive-show-in");if($!==null){try{let J=JSON.parse($);if(Array.isArray(J))return J.includes(Q)}catch{}return console.warn(`[phlex-reactive] malformed reactive_show in: list ${JSON.stringify($)} — skipped`),null}for(let J of n){let G=j.getAttribute(`data-reactive-show-${J}`);if(G!==null)return a(J,G,Q)}return console.warn("[phlex-reactive] a reactive_show binding declares no predicate — skipped"),null}var n=["gte","gt","lte","lt"];function a(j,Q,X){let Z=Number(Q);if(Number.isNaN(Z))return console.warn(`[phlex-reactive] reactive_show ${j}: needs a numeric literal, got ${JSON.stringify(Q)} — skipped`),null;let $=X==null?"":String(X).trim(),J=$===""?NaN:Number($);if(Number.isNaN(J))return!1;switch(j){case"gte":return J>=Z;case"gt":return J>Z;case"lte":return J<=Z;case"lt":return J<Z;default:return null}}function r(j,Q){if(!j||typeof j!=="object")return null;if(typeof j.equals==="string")return Q===j.equals;if(typeof j.not==="string")return Q!==j.not;if(Array.isArray(j.in))return j.in.includes(Q);for(let X of n)if(X in j)return a(X,j[X],Q);return null}var f="[data-reactive-show-field], [data-reactive-show]";function Rj(j){try{let Q=JSON.parse(j);if(Q&&typeof Q==="object"&&!Array.isArray(Q))return Q}catch{}return console.warn(`[phlex-reactive] malformed compound reactive_show payload ${JSON.stringify(j)} — skipped`),null}function w(j,Q){if(!j||typeof j!=="object"||typeof j.field!=="string")return!1;let X=Q(j.field)??"";return r(j,X)===!0}function t(j,Q){if(!Array.isArray(j)||j.length===0)return null;return j.some((X)=>Array.isArray(X)&&X.length>0&&X.every((Z)=>w(Z,Q)))}function Tj(j,Q){if(!j||typeof j!=="object")return null;let X=j.any;if(Array.isArray(X)&&(X.length===0||Array.isArray(X[0])))return t(X,Q);return Dj(j,Q)}function Dj(j,Q){let X=Array.isArray(j.all)?"all":Array.isArray(j.any)?"any":null;if(!X)return null;let Z=j[X];if(Z.length===0)return null;let $=Z.map((J)=>w(J,Q));return X==="all"?$.every(Boolean):$.some(Boolean)}function Ij(j){if(typeof j==="string"&&i.test(j))return!0;return console.warn(`[phlex-reactive] refused cross-root show target ${JSON.stringify(j)} — skipped`),!1}var Sj='a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';function kj(j){return j.querySelectorAll?.(Sj)?.[0]??null}function e(j){if(Array.isArray(j))return j;if(typeof j!=="string")return[];try{let Q=JSON.parse(j);return Array.isArray(Q)?Q:[]}catch{return[]}}function jj(j,Q){for(let X of j){if(!Array.isArray(X))continue;let[Z,$={}]=X;if(!Object.hasOwn(y,Z)){console.warn(`[phlex-reactive] unknown client op ${JSON.stringify(Z)} — skipped`);continue}for(let J of Q($))y[Z](J,$)}}function wj(j,Q){let X=j.to;if(Q){if(X==="@root")return[Q];if(typeof X!=="string"||X==="")return[];if(j.global)return[...document.querySelectorAll(X)];return[...Q.querySelectorAll(X)]}if(typeof X!=="string"||X===""||X==="@root")return[];return[...document.querySelectorAll(X)]}class sj extends Qj{static values={token:String};#R;#L=new Map;#K=new Map;#e;#B;#T;#x=0;#q=new Map;#O=new WeakMap;#N=new Map;#D=new WeakSet;#Y;#U;#H;#j;#X;#W;#V;connect(){if(E=!0,this.element.id==="")console.warn("[phlex-reactive] a reactive root has no id; its next-action token can't self-match "+"and may fall back to the first token in the response → a silent HTTP 403 on the NEXT action. "+"Put id: on the SAME element as reactive_attrs — use div(**reactive_root) (emits id + token together), "+"or div(id:, **reactive_attrs). The id: must NOT be on a child. See the README.");if(this.element.getAttribute?.("data-reactive-defer-token"))this.#I(),this.#V=()=>this.#I(),this.element.addEventListener?.("turbo:morph-element",this.#V);if(this.#jj()){if(this.#Y=()=>this.#F(),this.element.addEventListener?.("turbo:morph-element",this.#Y),this.#F(),this.element.getAttribute?.("data-reactive-warn-unsaved")==="true")this.#Bj()}if(this.#Oj())this.#j=()=>this.#g(),this.element.addEventListener?.("input",this.#j),this.element.addEventListener?.("change",this.#j),this.element.addEventListener?.("turbo:morph-element",this.#j),this.#g();if(this.#Cj())this.#X=(j)=>{if(j?.type==="input"&&!this.#Tj(j))return;this.#l()},this.element.addEventListener?.("input",this.#X),this.element.addEventListener?.("turbo:morph-element",this.#X),this.#l();if(this.#Rj())this.#W=()=>this.recompute(),this.element.addEventListener?.("turbo:morph-element",this.#W),this.recompute()}#jj(){if((this.element.getAttribute?.("data-action")??"").includes("reactive#trackDirty"))return!0;let j=this.element.querySelectorAll?.('[data-action*="reactive#trackDirty"]')??[];for(let Q of j)if(this.#Q(Q))return!0;return!1}disconnect(){if(this.#qj(),this.#Uj(),this.#xj(),this.#Fj(),this.#Dj(),this.#Ij(),this.#V)this.element.removeEventListener?.("turbo:morph-element",this.#V)}#I(){let j=this.element;if(!j?.id)return;let Q=j.getAttribute?.("data-reactive-defer-token");if(!Q)return;if(j.getAttribute?.("data-reactive-defer-pending")!=="true")return;P(j.id,Q)}dispatch(j){let{action:Q,params:X,debounce:Z,throttle:$,confirm:J,confirmWhen:G,outside:K,window:z,optimistic:Y}=j.params;if(!Q)return;let H=j.params.busy??this.#uj(j.params.loading);if(K&&this.element.contains(j.target))return;let L=j.currentTarget??j.target;if(!z&&!this.#wj(Y,L))j.preventDefault();let W=this.#p(J,G);if(!W)return this.#b(L,Q,X,Z,$,Y,H);Promise.resolve().then(()=>b(W)).catch(()=>!1).then((_)=>{if(_)this.#b(L,Q,X,Z,$,Y,H)})}runOps(j){let{ops:Q,confirm:X,confirmWhen:Z,outside:$,window:J}=j.params;if($&&this.element.contains(j.target))return;if(!J)j.preventDefault();let G=this.#p(X,Z);if(!G)return this.#i(this.#s(Q));Promise.resolve().then(()=>b(G)).catch(()=>!1).then((K)=>{if(K)this.#i(this.#s(Q))})}trackDirty(){this.#F()}recompute(j){if(j&&this.#D.has(j))return;let Q=this.#Xj(),X=Q.map(([q])=>q),Z=this.element.getAttribute?.("data-reactive-scope")||null,$=(q)=>Z&&!q.includes("[")?`${Z}[${q}]`:q,J=this.#_(),G=new Map,K=(q)=>{if(G.has(q))return G.get(q);let U=null;for(let M of this.element.querySelectorAll(`[name="${$(q)}"]`))if(J(M)){U=M;break}return G.set(q,U),U};for(let q of X)this.#k(q,K(q)?.value??"");let z=this.element.getAttribute("data-reactive-compute-reducer-param"),Y=z?Xj(z):null;if(!Y){this.#w({},K);return}let H=this.#Qj("data-reactive-compute-outputs-param"),L={};for(let[q,U]of Q){let M=K(q);if(U==="string")L[q]=M?.value??"";else{let N=Number(M?.value);L[q]=Number.isFinite(N)?N:0}}let W=Y(L,{changed:this.#Zj(j,X,Z)})||{},_=[];for(let q of H){if(!(q in W))continue;let U=K(q);if(!U)continue;if(String(W[q])===U.value)continue;U.value=W[q],_.push(U)}for(let q of Object.keys(W)){let U=W[q];if(U===void 0||U===null)continue;this.#k(q,U)}this.#w(W,K);for(let q of _){let U=new Event("input",{bubbles:!0});this.#D.add(U),q.dispatchEvent(U)}}listnavNext(j){this.#S(j,1)}listnavPrev(j){this.#S(j,-1)}listnavPick(j){let Q=this.#P(j),X=Q.findIndex((Z)=>Z.hasAttribute("data-reactive-highlighted"));if(X<0)return;j.preventDefault(),Q[X].click()}listnavClose(j){for(let Q of this.#P(j))Q.removeAttribute("data-reactive-highlighted")}#S(j,Q){let X=this.#P(j);if(!X.length)return;j.preventDefault();let Z=X.findIndex((G)=>G.hasAttribute("data-reactive-highlighted")),$=Z<0?Q>0?0:X.length-1:(Z+Q+X.length)%X.length;for(let G of X)G.removeAttribute("data-reactive-highlighted");let J=X[$];J.setAttribute("data-reactive-highlighted","true"),J.scrollIntoView?.({block:"nearest"})}#P(j){let X=(j?.currentTarget??j?.target??this.element).getAttribute?.("data-reactive-listnav-option-param")??this.element.getAttribute("data-reactive-listnav-option-param");if(!X)return[];let Z=this.#_();return Array.from(this.element.querySelectorAll(X)).filter(($)=>!$.hidden&&Z($))}#Qj(j){let Q=this.element.getAttribute(j);if(!Q)return[];try{let X=JSON.parse(Q);return Array.isArray(X)?X:[]}catch{return[]}}#Xj(){let j=this.element.getAttribute("data-reactive-compute-inputs-param");if(!j)return[];try{let Q=JSON.parse(j);if(Array.isArray(Q))return Q.map((X)=>[X,"number"]);if(Q&&typeof Q==="object")return Object.entries(Q);return[]}catch{return[]}}#Zj(j,Q,X){let Z=j?.target;if(!Z?.name||typeof Z.closest!=="function")return null;let $=this.#$j(Z.name,X);if(!Q.includes($))return null;return this.#Q(Z)?$:null}#$j(j,Q){if(!Q)return j;let X=`${Q}[`;return j.startsWith(X)&&j.endsWith("]")?j.slice(X.length,-1):j}#k(j,Q){let X=String(Q);for(let Z of this.#Jj(j)){if(Z.textContent===X)continue;Z.textContent=X}}#Jj(j){let Q=this.element.querySelectorAll(`[data-reactive-text="${j}"]`);return Array.from(Q).filter((X)=>this.#Q(X))}#w(j,Q){let X=this.#Gj();for(let[Z,$]of Object.entries(X)){let J=Z in j?j[Z]:Q(Z)?.value;if(J===void 0||J===null)continue;let G=String(J);for(let K of Array.isArray($)?$:[$]){if(!Fj(K))continue;for(let z of document.querySelectorAll(K)){if(z.textContent===G)continue;z.textContent=G}}}}#Gj(){let j=this.element.getAttribute("data-reactive-compute-mirror-param");if(!j)return{};try{let Q=JSON.parse(j);return Q&&typeof Q==="object"&&!Array.isArray(Q)?Q:{}}catch{return{}}}#b(j,Q,X,Z,$,J,G){if(this.#M("reactive:before-dispatch",{action:Q,params:this.#o(X),element:this.element},{cancelable:!0}).defaultPrevented)return;let z=Number(Z)||0;if(z>0)return this.#Kj(j,z,Q,X,J,G);let Y=Number($)||0;if(Y>0)return this.#Yj(j,Y,Q,X,J,G);return this.#A(Q,X,J,j,G)}#A(j,Q,X,Z,$){let J=this.#bj(X,Z),G=this.#vj(j,Z,$),K=this.#v()?this.#zj(X,Z):null;return this.queue=(this.queue??Promise.resolve()).then(()=>this.#Vj(j,Q,J,G,K)),this.queue}#zj(j,Q){if(!j?.hide)return null;let X=this.#r(j,Q);if(!X.length)return null;return()=>{let Z=X.filter(($)=>$.isConnected&&!$.hidden);if(!Z.length)return;console.warn("[phlex-reactive] optimistic: { hide: true } was undone by the reply's re-render — "+"the element is visible again. For an instant delete, return reply.remove so the server removes it; otherwise the hide only flashes.",Z)}}#Kj(j,Q,X,Z,$,J){this.#E(j);let G=()=>{this.#E(j),this.#A(X,Z,$,j,J)},K=setTimeout(G,Q);j?.addEventListener?.("blur",G,{once:!0}),this.#L.set(j,{timer:K,flush:G})}#E(j){let Q=this.#L.get(j);if(!Q)return;clearTimeout(Q.timer),j?.removeEventListener?.("blur",Q.flush),this.#L.delete(j)}#qj(){for(let j of[...this.#L.keys()])this.#E(j)}#Yj(j,Q,X,Z,$,J){let G=this.#K.get(j)??new Map;if(G.has(X))return;let K=setTimeout(()=>{if(G.delete(X),G.size===0)this.#K.delete(j)},Q);return G.set(X,K),this.#K.set(j,G),this.#A(X,Z,$,j,J)}#Uj(){for(let j of this.#K.values())for(let Q of j.values())clearTimeout(Q);this.#K.clear()}#M(j,Q,{cancelable:X=!1}={}){let Z=new CustomEvent(j,{bubbles:!0,composed:!0,cancelable:X,detail:Q});return(this.element.isConnected?this.element:document).dispatchEvent(Z),Z}#Z(j,Q,X,Z){let $=()=>{if(!this.element.isConnected){console.warn("[phlex-reactive] retry() ignored — the reactive root left the DOM");return}return this.#A(j,Q)};this.#M("reactive:error",{action:j,params:X,...Z,retry:$})}#$(j){if(this.element?.isConnected===!1)return;this.element?.setAttribute?.("data-reactive-error",j)}#Hj(){this.element?.removeAttribute?.("data-reactive-error")}#Wj(){let j=document.querySelector("[data-reactive-error-flash]");if(!j?.content)return;let Q=j.getAttribute("data-reactive-error-flash")||"flash",X=document.getElementById(Q);if(!X)return;X.appendChild(j.content.cloneNode(!0))}#_j(){if(typeof sessionStorage>"u")return Promise.resolve();let j=Number(sessionStorage.getItem(k));if(!Number.isFinite(j)||j<=0)return Promise.resolve();if(!x)x=!0,console.warn(`[phlex-reactive] latency simulator ACTIVE — every action is delayed by ${j}ms. Call PhlexReactive.disableLatencySim() (or clear sessionStorage) to turn it off.`);return new Promise((Q)=>setTimeout(Q,j))}#v(){return this.element?.getAttribute?.("data-reactive-debug")==="true"}#h(){return typeof performance<"u"&&typeof performance.now==="function"?performance.now():Date.now()}#Lj(j){if(!j)return[];let Q=[],X=/<turbo-stream\b([^>]*)>/g,Z;while((Z=X.exec(j))!==null){let $=Z[1],J=$.match(/\baction="([^"]*)"/)?.[1]??"?",G=$.match(/\btarget="([^"]*)"/)?.[1];Q.push(G?`${J} → #${G}`:J)}return Q}#Nj(j){let{action:Q,status:X,ms:Z}=j,J=`reactive ${this.element?.id?`#${this.element.id} `:""}${Q} → ${X??"—"} (${Math.round(Z)}ms)`;if(console.groupCollapsed(J),console.log(`params: [${j.paramNames.join(", ")}] + collected: [${j.fieldNames.join(", ")}]`),console.log(`encoding: ${j.encoding}`),j.streams.length)console.log(`streams: ${j.streams.join(" ")}`);console.log(`token: ${j.tokenRefreshed?"refreshed ✓":"unchanged"}`),console.groupEnd()}async#Vj(j,Q,X,Z,$){let{fields:J,files:G}=this.#u(),K=this.#o(Q),z={...J,...K},Y=this.#J,H=G.length>0,L=H?this.#Sj(Y,j,z,G):JSON.stringify({token:Y,act:j,params:z}),W=this.#v()?{action:j,paramNames:Object.keys(K),fieldNames:Object.keys(J),encoding:H?"multipart":"json",status:null,streams:[],tokenRefreshed:!1,started:this.#h()}:null;await this.#_j();try{if(navigator.onLine===!1){this.#G(X),this.#$("offline"),this.#Z(j,Q,z,{kind:"offline"});return}let _;try{let N={Accept:"text/vnd.turbo-stream.html","X-CSRF-Token":this.#cj()};if(!H)N["Content-Type"]="application/json";let B=this.#dj();if(B)N["X-Pgbus-Connection"]=B;_=await fetch(this.#mj(),{method:"POST",headers:N,body:L,credentials:"same-origin",signal:AbortSignal.timeout(this.#gj())})}catch(N){if(console.error("[phlex-reactive] action error",N),this.#G(X),N?.name==="TimeoutError"||N?.name==="AbortError"){this.#$("timeout"),this.#Z(j,Q,z,{kind:"timeout"});return}this.#Wj(),this.#$("network"),this.#Z(j,Q,z,{kind:"network"});return}if(W)W.status=_.status;if(_.redirected){console.error("[phlex-reactive] action was redirected (auth/CSRF?) — no update applied"),this.#G(X),this.#$("redirected"),this.#Z(j,Q,z,{kind:"redirected",status:_.status});return}if(!_.ok){let N=await _.text();if(console.error(`[phlex-reactive] action failed: HTTP ${_.status}`,N),this.#G(X),(_.headers.get("Content-Type")||"").includes("turbo-stream")){let B=this.#f(N);if(this.#J=B??this.#J,W)this.#y(W,N,B);window.Turbo.renderStreamMessage(N)}this.#$("http"),this.#Z(j,Q,z,{kind:"http",status:_.status,body:N});return}let q=_.headers.get("Content-Type")||"";if(!q.includes("turbo-stream")){console.error(`[phlex-reactive] expected a turbo-stream, got "${q}" — no update applied`),this.#G(X),this.#$("content-type"),this.#Z(j,Q,z,{kind:"content-type",status:_.status});return}let U=await _.text(),M=this.#f(U);if(this.#J=M??this.#J,W)this.#y(W,U,M);if(window.Turbo.renderStreamMessage(U),$)queueMicrotask($);this.#Hj(),this.#M("reactive:applied",{action:j,params:z,html:U})}catch(_){console.error("[phlex-reactive] action error",_),this.#G(X),this.#M("reactive:error",{action:j,params:z,kind:"apply"})}finally{if(Z?.(),W)this.#Nj({...W,ms:this.#h()-W.started})}}#y(j,Q,X){j.streams=this.#Lj(Q),j.tokenRefreshed=X!=null}get#J(){return this.#R??this.tokenValue}set#J(j){this.#R=j}#f(j){let Q=this.element.id;if(!Q)return j.match(/data-reactive-token-value="([^"]+)"/)?.[1];let{token:X,self:Z}=this.#Aj(Q),$=j.match(X);if($)return $[1];let J=j.match(Z);if(J)return J[1].match(/data-reactive-token-value="([^"]+)"/)?.[1];return}#Aj(j){let Q=this.#T;if(Q&&Q.id===j)return Q;let X=Bj(j);return this.#T={id:j,token:new RegExp(`<turbo-stream\\b[^>]*\\baction="reactive:token"[^>]*\\btarget="${X}"[^>]*\\bdata-reactive-token-value="([^"]+)"`),self:new RegExp(`<turbo-stream\\b[^>]*\\baction="(?:replace|update)"[^>]*\\btarget="${X}"[^>]*>([\\s\\S]*?)</turbo-stream>`)}}#Q(j){return j.closest('[data-controller~="reactive"]')===this.element}#_(){if(this.element.querySelectorAll('[data-controller~="reactive"]').length===0)return()=>!0;return(Q)=>this.#Q(Q)}#p(j,Q){if(j)return j;if(!Q)return null;let X=Q;if(typeof Q==="string")try{X=JSON.parse(Q)}catch{return console.warn(`[phlex-reactive] malformed conditional confirm payload ${JSON.stringify(Q)} — skipped`),null}if(!X||typeof X!=="object")return null;let{fields:Z}=this.#u(),$=(G)=>Z[G],J;if(typeof X.predicate==="string"){let G=Zj(X.predicate);if(!G)return console.warn(`[phlex-reactive] confirm predicate "${X.predicate}" is not registered — proceeding without a dialog (register it with setConfirmPredicate)`),null;J=!!G(Z)}else J=t(X.groups?.any,$)===!0;return J?X.message:null}#u(){let j={},Q=[],X=this.#_();return this.element.querySelectorAll("input[name], select[name], textarea[name]").forEach((Z)=>{if(!X(Z))return;if(Z.type==="file")for(let $ of Z.files??[])Q.push({name:Z.name,file:$,multiple:Z.multiple});else if(Z.type==="checkbox")j[Z.name]=Z.checked;else if(Z.type==="radio"){if(Z.checked)j[Z.name]=Z.value}else j[Z.name]=Z.value}),this.element.querySelectorAll("[name]:is(lexxy-editor, trix-editor, [contenteditable=''], [contenteditable=true], [contenteditable=plaintext-only])").forEach((Z)=>{if(!X(Z))return;let $=Z.getAttribute("name");if(!$)return;let J=j[$];if(J==null||J==="")j[$]=Z.value??Z.textContent??Z.innerHTML??""}),{fields:j,files:Q}}#F(){if(typeof this.element?.querySelectorAll!=="function")return;let j=0;if(this.element.querySelectorAll("input[name], select[name], textarea[name]").forEach((Q)=>{if(!this.#Q(Q))return;if(Q.type==="file")return;if(this.#Mj(Q))Q.setAttribute("data-reactive-dirty","true"),j++;else Q.removeAttribute("data-reactive-dirty")}),j>0)this.element.setAttribute("data-reactive-dirty",String(j));else this.element.removeAttribute("data-reactive-dirty")}#Mj(j){if(j.type==="checkbox"||j.type==="radio")return j.checked!==j.defaultChecked;if(j.tag==="select"||j.options)return Array.from(j.options??[]).some((Q)=>Q.selected!==Q.defaultSelected);return j.value!==j.defaultValue}#m(){let j=this.element.getAttribute?.("data-reactive-dirty"),Q=Number(j);return Number.isFinite(Q)&&Q>0?Q:0}#Bj(){if(typeof window>"u"||typeof window.addEventListener!=="function")return;this.#U=(j)=>{if(this.#m()===0)return;return j.preventDefault(),j.returnValue="You have unsaved changes.",j.returnValue},this.#H=(j)=>{if(this.#m()===0)return;if(!(typeof window.confirm==="function"?window.confirm("You have unsaved changes. Leave anyway?"):!0))j.preventDefault?.()},window.addEventListener("beforeunload",this.#U),window.addEventListener("turbo:before-visit",this.#H)}#xj(){if(this.#Y)this.element.removeEventListener?.("turbo:morph-element",this.#Y),this.#Y=void 0;if(typeof window<"u"&&typeof window.removeEventListener==="function"){if(this.#U)window.removeEventListener("beforeunload",this.#U);if(this.#H)window.removeEventListener("turbo:before-visit",this.#H)}this.#U=void 0,this.#H=void 0}#Oj(){if(this.element.getAttribute?.("data-reactive-show-targets"))return!0;let j=this.element.querySelectorAll?.(f)??[];for(let Q of j)if(this.#Q(Q))return!0;return!1}#g(){if(typeof this.element?.querySelectorAll!=="function")return;let j=this.#_(),Q=this.element.getAttribute?.("data-reactive-scope")||null,X=new Map,Z=($)=>{if(!X.has($))X.set($,this.#d($,j,Q));return X.get($)};for(let $ of this.element.querySelectorAll(f)){if(!j($))continue;let J=$.getAttribute("data-reactive-show");if(J!==null){let Y=Tj(Rj(J),Z);if(Y!==null)this.#c($,Y,j,Q);continue}let G=$.getAttribute("data-reactive-show-field");if(!G)continue;let K=Z(G);if(K===null)continue;let z=Cj($,K);if(z===null)continue;this.#c($,z,j,Q)}this.#Pj(j,X,Q)}#c(j,Q,X,Z){if(j.hidden=!Q,j.getAttribute("data-reactive-show-disable")!=="true")return;if(typeof j.querySelectorAll!=="function")return;for(let $ of j.querySelectorAll("input[name], select[name], textarea[name]"))if(X($))$.disabled=!Q;if(j.name&&X(j))j.disabled=!Q}#Pj(j,Q,X){let Z=this.#Ej();for(let[$,J]of Object.entries(Z)){if(!J||typeof J!=="object"||Array.isArray(J))continue;if(!Q.has($))Q.set($,this.#d($,j,X));let G=Q.get($);if(G===null)continue;let K=()=>G;for(let[z,Y]of Object.entries(J)){if(!Ij(z))continue;let H;if(Array.isArray(Y)){if(Y.length===0){console.warn(`[phlex-reactive] malformed reactive_show_targets group for ${z} — skipped`);continue}H=Y.every((L)=>w(L,K))}else{let L=r(Y,G);if(L===null){console.warn(`[phlex-reactive] malformed reactive_show_targets predicate for ${z} — skipped`);continue}H=L}for(let L of document.querySelectorAll(z))L.hidden=!H}}}#Ej(){let j=this.element.getAttribute?.("data-reactive-show-targets");if(!j)return{};try{let Q=JSON.parse(j);if(Q&&typeof Q==="object"&&!Array.isArray(Q))return Q}catch{}return console.warn("[phlex-reactive] malformed data-reactive-show-targets — ignored. "+"Did two reactive_show_targets calls collide on one root? Declare every field in ONE call: reactive_show_targets(mode: { ... }, kind: { ... })"),{}}#d(j,Q,X){let Z=X&&!j.includes("[")?`${X}[${j}]`:j,$=!1,J=null;for(let G of this.element.querySelectorAll(`[name="${Z}"]`)){if(!Q(G))continue;if(G.type==="checkbox")return G.checked?"true":"false";if(G.type==="radio"){if(G.checked)return G.value??"";$=!0;continue}J??=G}if(J)return J.value??"";return $?"":null}#Fj(){if(!this.#j)return;this.element.removeEventListener?.("input",this.#j),this.element.removeEventListener?.("change",this.#j),this.element.removeEventListener?.("turbo:morph-element",this.#j),this.#j=void 0}#Cj(){return!!(this.element.getAttribute?.("data-reactive-filter-input")&&this.element.getAttribute?.("data-reactive-filter-option"))}#Rj(){return this.element.getAttribute?.("data-reactive-compute-seed")==="true"}#Tj(j){let Q=this.element.getAttribute("data-reactive-filter-input");return!!Q&&typeof j.target?.matches==="function"&&j.target.matches(Q)}#l(){if(typeof this.element?.querySelectorAll!=="function")return;let j=this.element.getAttribute("data-reactive-filter-input"),Q=this.element.getAttribute("data-reactive-filter-option");if(!j||!Q)return;let X=this.#_(),Z=[...this.element.querySelectorAll(j)].find(X);if(!Z)return;let $=(Z.value??"").trim().toLowerCase(),J=0;for(let z of this.element.querySelectorAll(Q)){if(!X(z))continue;let Y=(z.getAttribute("data-reactive-filter-text")??z.textContent??"").toLowerCase(),H=$!==""&&!Y.includes($);if(z.hidden=H,H)z.removeAttribute("data-reactive-highlighted");else J++}let G=this.element.getAttribute("data-reactive-filter-group");if(G)for(let z of this.element.querySelectorAll(G)){if(!X(z))continue;let Y=[...z.querySelectorAll(Q)].filter(X);if(Y.length===0)continue;z.hidden=Y.every((H)=>H.hidden)}let K=this.element.getAttribute("data-reactive-filter-empty");if(K){for(let z of this.element.querySelectorAll(K))if(X(z))z.hidden=J>0}}#Dj(){if(!this.#X)return;this.element.removeEventListener?.("input",this.#X),this.element.removeEventListener?.("turbo:morph-element",this.#X),this.#X=void 0}#Ij(){if(!this.#W)return;this.element.removeEventListener?.("turbo:morph-element",this.#W),this.#W=void 0}#Sj(j,Q,X,Z){let $=new FormData;$.append("token",j),$.append("act",Q);for(let[G,K]of Object.entries(X))this.#C($,`params[${G}]`,K);let J=this.#kj(Z);for(let{name:G,file:K,multiple:z}of Z){let H=z||J.has(G)?`params[${G}][]`:`params[${G}]`;$.append(H,K,K.name)}return $}#C(j,Q,X){if(X==null)j.append(Q,"");else if(Array.isArray(X))X.forEach((Z,$)=>this.#C(j,`${Q}[${$}]`,Z));else if(typeof X==="object")for(let[Z,$]of Object.entries(X))this.#C(j,`${Q}[${Z}]`,$);else j.append(Q,String(X))}#kj(j){let Q=new Map;for(let{name:X}of j)Q.set(X,(Q.get(X)??0)+1);return new Set([...Q].filter(([,X])=>X>1).map(([X])=>X))}#o(j){if(!j)return{};try{return typeof j==="string"?JSON.parse(j):j}catch{return{}}}#s(j){return e(j)}#i(j){jj(j,(Q)=>this.#n(Q))}#n(j){let Q=j.to;if(Q==="@root")return[this.element];if(typeof Q!=="string"||Q==="")return[];if(j.global)return[...document.querySelectorAll(Q)];return[...this.element.querySelectorAll(Q)].filter((X)=>this.#Q(X))}#wj(j,Q){if(j?.checked!=="keep")return!1;let X=Q?.type;return X==="checkbox"||X==="radio"}#bj(j,Q){if(!j)return null;let X=this.#a(j,Q,!0);return X.length?X:null}#G(j){if(!j)return;if(!this.element.isConnected)return;for(let Q of j)Q()}#vj(j,Q,X){this.#yj(j,Q);let Z=X?this.#a(X,Q,!1):[];l();let $=!1;return()=>{if($)return;$=!0,this.#fj(j,Q),o();for(let J of Z)J()}}#a(j,Q,X){let Z=[];for(let $ of this.#r(j,Q)){if(j.add_class){let J=j.add_class.filter((G)=>!$.classList.contains(G));if($.classList.add(...J),J.length)Z.push(()=>$.classList.remove(...J))}if(j.remove_class){let J=j.remove_class.filter((G)=>$.classList.contains(G));if($.classList.remove(...J),J.length)Z.push(()=>$.classList.add(...J))}if(j.toggle_class)j.toggle_class.forEach((J)=>$.classList.toggle(J)),Z.push(()=>j.toggle_class.forEach((J)=>$.classList.toggle(J)));if(j.hide)$.hidden=!0,Z.push(()=>$.hidden=!1);if(j.show)$.hidden=!1,Z.push(()=>$.hidden=!0)}if(Q&&(j.disable||j.text!=null))Z.push(this.#hj(j,Q));if(X&&j.checked==="keep"&&Q&&"checked"in Q){let $=Q.checked;Z.push(()=>Q.checked=!$)}return Z}#r(j,Q){if(j.to==null)return Q?[Q]:[];return this.#n({to:j.to})}#hj(j,Q){let X=this.#N.get(Q);if(X)X.count++;else this.#N.set(Q,{count:1,disabled:Q.disabled,html:Q.innerHTML,hadText:j.text!=null,swappedTo:j.text});if(j.disable)Q.disabled=!0;if(j.text!=null)Q.innerHTML=j.text;return()=>this.#pj(Q,j)}#yj(j,Q){if(this.#z(Q,j,1),this.#z(this.element,j,1),this.#q.set(j,(this.#q.get(j)??0)+1),this.#x++===0)this.element.setAttribute("aria-busy","true");for(let X of this.#t(j))this.#z(X,j,1)}#fj(j,Q){this.#z(Q,j,-1),this.#z(this.element,j,-1);let X=(this.#q.get(j)??1)-1;if(X<=0)this.#q.delete(j);else this.#q.set(j,X);if(--this.#x<=0)this.#x=0,this.element.removeAttribute("aria-busy");for(let Z of this.#t(j))this.#z(Z,j,-1)}#z(j,Q,X){if(!j||typeof j.getAttribute!=="function")return;let Z=this.#O.get(j)??new Map,$=(Z.get(Q)??0)+X;if($<=0)Z.delete(Q);else Z.set(Q,$);if(Z.size===0){this.#O.delete(j),j.removeAttribute("data-reactive-busy");return}this.#O.set(j,Z),j.setAttribute("data-reactive-busy",[...Z.keys()].join(" "))}#t(j){return[...this.element.querySelectorAll?.("[data-reactive-busy-on]")??[]].filter((X)=>X.getAttribute("data-reactive-busy-on")===j&&this.#Q(X))}#pj(j,Q){let X=this.#N.get(j);if(!X)return;if(--X.count>0)return;if(this.#N.delete(j),!j.isConnected)return;if(Q.disable)j.disabled=X.disabled;if(X.hadText&&j.innerHTML===X.swappedTo)j.innerHTML=X.html}#uj(j){if(!j||typeof j!=="object")return null;let{class:Q,...X}=j;return Q==null?j:{...X,add_class:Q}}#mj(){return this.#e??=document.querySelector('meta[name="phlex-reactive-action-path"]')?.content||"/reactive/actions"}#gj(){if(this.#B!=null)return this.#B;let j=document.querySelector('meta[name="phlex-reactive-timeout"]')?.content,Q=Number(j);return this.#B=Number.isFinite(Q)&&Q>0?Q:30000}#cj(){return document.querySelector('meta[name="csrf-token"]')?.content??""}#dj(){return document.querySelector("pgbus-stream-source[connection-id]")?.getAttribute("connection-id")||document.querySelector('meta[name="pgbus-connection-id"]')?.content||null}}export{fj as resetReactiveDefers,dj as resetReactiveActivity,$j as registerReactiveVisit,Jj as registerReactiveToken,Nj as registerReactiveOffline,Gj as registerReactiveJs,_j as registerReactiveDismiss,zj as registerReactiveDefer,h as registerReactiveActions,cj as reactiveActivityCount,pj as pendingDeferVia,o as exitReactiveActivity,Bj as escapeRegExp,l as enterReactiveActivity,Vj as enableLatencySim,Aj as disableLatencySim,sj as default,xj as checkReactiveRegistration,lj as __resetReactiveRegistrationForTest,mj as __resetReactiveOfflineForTest,gj as __resetReactiveLatencyForTest,uj as __resetReactiveDismissForTest,oj as __markReactiveConnectedForTest,k as LATENCY_KEY,d as ACTIVE_ATTR};
|
|
1
|
+
import{Controller as Xj}from"@hotwired/stimulus";import{confirmResolver as w}from"phlex/reactive/confirm";import{computeReducer as Zj}from"phlex/reactive/compute";import{confirmPredicate as $j}from"phlex/reactive/confirm_predicate";function Jj(){let j=window.Turbo?.StreamActions;if(!j||j["reactive:visit"])return;j["reactive:visit"]=function(){let X=this.getAttribute("data-url");if(X)window.Turbo.visit(X,{action:"advance"})}}function Qj(){let j=window.Turbo?.StreamActions;if(!j||j["reactive:token"])return;j["reactive:token"]=function(){let X=this.getAttribute("data-reactive-token-value"),Z=this.getAttribute("target");if(!X||!Z)return;let $=document.getElementById(Z);if($)$.setAttribute("data-reactive-token-value",X)}}function Gj(){let j=window.Turbo?.StreamActions;if(!j||j["reactive:js"])return;j["reactive:js"]=function(){let X=e(this.getAttribute("data-reactive-ops"));if(!X.length)return;let Z=this.getAttribute("target"),$=Z?document.getElementById(Z):null;if(Z&&!$)return;jj(X,(J)=>bj(J,$))}}var N=new Map;function p(j,X){let Z=!N.has(j);if(N.set(j,X),Z)l()}function x(j){if(N.delete(j))o()}function fj(){N.clear(),T=!1}function pj(j){return N.get(j)?.via}var T=!1;function zj(){let j=window.Turbo?.StreamActions;if(!j||j["reactive:defer"])return;if(j["reactive:defer"]=function(){let X=this.getAttribute("target");if(!X)return;if(this.getAttribute("data-reactive-defer-via")==="stream"){qj(X,this);return}let Z=this.getAttribute("data-reactive-defer-token");if(!Z)return;P(X,Z)},!T&&typeof document<"u"&&document.addEventListener)T=!0,document.addEventListener("turbo:before-stream-render",Kj)}function Kj(j){let Z=j.target?.getAttribute?.("target");if(!Z)return;let $=Z.startsWith("reactive-defer-src-")?Z.slice(19):Z;if(N.get($)?.via==="stream")x($)}function P(j,X){let Z=document.getElementById(j);if(!Z){console.warn(`[phlex-reactive] reactive:defer target #${j} is not on the page — skipped`);return}m(j),g(Z);let $={via:"fetch",abort:new AbortController,timedOut:!1};p(j,$),Yj(j,$,X)}function qj(j,X){let Z=document.getElementById(j);if(!Z){console.warn(`[phlex-reactive] reactive:defer target #${j} is not on the page — skipped`);return}let $=X.getAttribute("data-reactive-defer-src");if(!$)return;if(!globalThis.customElements?.get?.("pgbus-stream-source")){let Q=X.getAttribute("data-reactive-defer-token");if(Q){P(j,Q);return}console.error("[phlex-reactive] reactive:defer via=stream but <pgbus-stream-source> is not registered "+"and no fallback token was provided — is the pgbus client loaded on this page?");return}m(j),g(Z);let J=document.createElement("pgbus-stream-source");J.id=u(j),J.setAttribute("src",$),J.setAttribute("since-id",X.getAttribute("data-reactive-defer-since-id")??"0"),J.setAttribute("hidden",""),document.body.appendChild(J),p(j,{via:"stream"})}function u(j){return`reactive-defer-src-${j}`}async function Yj(j,X,Z){let $=setTimeout(()=>{X.timedOut=!0,X.abort.abort()},Wj()),J;try{J=await fetch(Uj(),{method:"POST",headers:{Accept:"text/vnd.turbo-stream.html","Content-Type":"application/json","X-CSRF-Token":Hj()},body:JSON.stringify({token:Z}),credentials:"same-origin",signal:X.abort.signal})}catch(G){if(clearTimeout($),N.get(j)!==X)return;console.error("[phlex-reactive] deferred render failed",G),F(j,Z);return}if(N.get(j)!==X){clearTimeout($);return}if(J.status===204){clearTimeout($),h(j);return}if(!J.ok){clearTimeout($),console.error(`[phlex-reactive] deferred render failed: HTTP ${J.status}`),F(j,Z,J.status);return}let Q;try{Q=await J.text()}catch(G){if(clearTimeout($),N.get(j)!==X)return;console.error("[phlex-reactive] deferred render failed reading the body",G),F(j,Z);return}if(clearTimeout($),N.get(j)!==X)return;h(j),window.Turbo.renderStreamMessage(Q)}function m(j){let X=N.get(j);if(!X)return;if(x(j),X.via==="fetch")X.abort.abort();else document.getElementById(u(j))?.remove?.()}function g(j){j.setAttribute("data-reactive-defer-pending","true"),j.setAttribute("aria-busy","true")}function c(j){j.removeAttribute("data-reactive-defer-pending"),j.removeAttribute("aria-busy")}function h(j){x(j);let X=document.getElementById(j);if(!X)return;c(X),X.removeAttribute("data-reactive-error")}function F(j,X,Z){x(j);let $=document.getElementById(j);if(!$)return;c($),$.setAttribute("data-reactive-error","defer");let J=()=>{let Q=document.getElementById(j);if(!Q){console.warn("[phlex-reactive] defer retry() ignored — the target left the DOM");return}Q.removeAttribute("data-reactive-error"),P(j,X)};$.dispatchEvent(new CustomEvent("reactive:error",{bubbles:!0,composed:!0,detail:{kind:"defer",target:j,status:Z,retry:J}}))}function Uj(){return document.querySelector('meta[name="phlex-reactive-defer-path"]')?.content||"/reactive/defer"}function Hj(){return document.querySelector('meta[name="csrf-token"]')?.content??""}function Wj(){let j=document.querySelector('meta[name="phlex-reactive-timeout"]')?.content,X=Number(j);return Number.isFinite(X)&&X>0?X:30000}var I=!1;function _j(){if(I)return;if(typeof document>"u"||!document.addEventListener)return;I=!0,document.addEventListener("turbo:before-stream-render",Lj)}function Lj(j){let X=j.detail,Z=X?.render;if(typeof Z!=="function"||Z.__reactiveDismissWrapped){if(typeof requestAnimationFrame==="function")requestAnimationFrame(C);else setTimeout(C,0);return}let $=async(J)=>{await Z(J),C()};$.__reactiveDismissWrapped=!0,X.render=$}function C(){let j=document.querySelectorAll("[data-reactive-dismiss-after]");for(let X of j){if(X.hasAttribute("data-reactive-dismiss-scheduled"))continue;let Z=Number(X.getAttribute("data-reactive-dismiss-after"));if(!Number.isFinite(Z)||Z<=0)continue;X.setAttribute("data-reactive-dismiss-scheduled",""),setTimeout(()=>X.remove(),Z)}}function uj(){I=!1}var k=!1;function Vj(){if(k)return;if(typeof window>"u"||typeof document>"u")return;if(typeof window.addEventListener!=="function")return;k=!0;let j=()=>{let X=document.documentElement;if(typeof X?.toggleAttribute!=="function")return;X.toggleAttribute("data-reactive-offline",globalThis.navigator?.onLine===!1)};j(),window.addEventListener("online",j),window.addEventListener("offline",j)}function mj(){k=!1}var S="phlex-reactive:latency",O=!1;function Nj(j){if(typeof sessionStorage>"u")return;sessionStorage.setItem(S,String(j))}function Mj(){if(typeof sessionStorage>"u")return;sessionStorage.removeItem(S),O=!1}function Aj(){if(typeof window>"u"||typeof document>"u")return;if(document.querySelector?.('meta[name="phlex-reactive-env"]')?.content!=="development")return;window.PhlexReactive={enableLatencySim:Nj,disableLatencySim:Mj}}function gj(){O=!1}var d="data-reactive-active",M=0;function l(){if(M++,M===1)s("reactive:busy")}function o(){if(M===0)return;if(M--,M===0)s("reactive:idle")}function cj(){return M}function dj(){M=0,(typeof document<"u"?document.documentElement:null)?.removeAttribute?.(d)}function s(j){if(typeof document>"u")return;let X=document.documentElement;if(typeof X?.toggleAttribute==="function")X.toggleAttribute(d,M>0);if(typeof document.dispatchEvent==="function"&&typeof CustomEvent==="function")document.dispatchEvent(new CustomEvent(j,{detail:{count:M}}))}function y(){Jj(),Qj(),Gj(),zj(),_j(),Vj(),Aj()}function Bj(j){return j.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}if(typeof window<"u")if(window.Turbo)y();else document.addEventListener("turbo:load",y,{once:!0});var E=!1;function Oj(){if(E)return;if(typeof document>"u")return;let j=document.querySelectorAll('[data-controller~="reactive"]');if(!j||j.length===0)return;console.warn("[phlex-reactive] found "+j.length+' element(s) with data-controller="reactive" '+"but the reactive controller never connected. It is loaded but not registered — "+'add `application.register("reactive", ReactiveController)` (importmap) or import it into app/javascript/controllers/ for lazyLoadControllersFrom apps. See the README.')}function lj(){E=!1}function oj(){E=!0}if(typeof window<"u"&&typeof document<"u"){let j=()=>setTimeout(Oj,0);if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",j,{once:!0});else j()}var xj=new Set(["href","src","srcdoc","action","formaction","xlink:href","style"]);function Pj(j){let X=String(j).toLowerCase();return X.startsWith("on")||xj.has(X)}function Ej(j,X,Z){let[$,J,Q]=X;j.classList.add($,J),Z(),requestAnimationFrame(()=>{j.classList.remove(J),j.classList.add(Q)});let G=!1,K=()=>{if(G)return;G=!0,j.classList.remove($,Q)};j.addEventListener("animationend",K,{once:!0}),setTimeout(K,350)}var v=Object.freeze({show:(j,X)=>R(j,!1,X),hide:(j,X)=>R(j,!0,X),toggle:(j,X)=>R(j,!j.hidden,X),add_class:(j,X)=>j.classList.add(...X.classes??[]),remove_class:(j,X)=>j.classList.remove(...X.classes??[]),toggle_class:(j,X)=>(X.classes??[]).forEach((Z)=>j.classList.toggle(Z)),set_attr:(j,X)=>{if(D(X.name))j.setAttribute(X.name,X.value??"")},remove_attr:(j,X)=>{if(D(X.name))j.removeAttribute(X.name)},toggle_attr:(j,X)=>{if(!D(X.name))return;if(j.hasAttribute(X.name))j.removeAttribute(X.name);else j.setAttribute(X.name,"")},focus:(j)=>j.focus?.(),focus_first:(j)=>Sj(j)?.focus?.(),text:(j,X)=>{let Z=String(X.value??"");if(j.textContent!==Z)j.textContent=Z},dispatch:(j,X)=>{j.dispatchEvent(new CustomEvent(X.name,{bubbles:!0,composed:!0,detail:X.detail??{}}))}});function R(j,X,Z){if(Z?.transition)Ej(j,Z.transition,()=>j.hidden=X);else j.hidden=X}function D(j){if(!Pj(j))return!0;return console.warn(`[phlex-reactive] refused client attr op on ${JSON.stringify(j)} — skipped`),!1}var i=/^#[A-Za-z_][\w-]*$/;function Fj(j){if(typeof j==="string"&&i.test(j))return!0;return console.warn(`[phlex-reactive] refused cross-root mirror target ${JSON.stringify(j)} — skipped`),!1}function Cj(j,X){let Z=j.getAttribute("data-reactive-show-equals");if(Z!==null)return X===Z;let $=j.getAttribute("data-reactive-show-not");if($!==null)return X!==$;let J=j.getAttribute("data-reactive-show-in");if(J!==null){try{let Q=JSON.parse(J);if(Array.isArray(Q))return Q.includes(X)}catch{}return console.warn(`[phlex-reactive] malformed reactive_show in: list ${JSON.stringify(J)} — skipped`),null}for(let Q of n){let G=j.getAttribute(`data-reactive-show-${Q}`);if(G!==null)return a(Q,G,X)}return console.warn("[phlex-reactive] a reactive_show binding declares no predicate — skipped"),null}var n=["gte","gt","lte","lt"];function a(j,X,Z){let $=Number(X);if(Number.isNaN($))return console.warn(`[phlex-reactive] reactive_show ${j}: needs a numeric literal, got ${JSON.stringify(X)} — skipped`),null;let J=Z==null?"":String(Z).trim(),Q=J===""?NaN:Number(J);if(Number.isNaN(Q))return!1;switch(j){case"gte":return Q>=$;case"gt":return Q>$;case"lte":return Q<=$;case"lt":return Q<$;default:return null}}function r(j,X){if(!j||typeof j!=="object")return null;if(typeof j.equals==="string")return X===j.equals;if(typeof j.not==="string")return X!==j.not;if(Array.isArray(j.in))return j.in.includes(X);for(let Z of n)if(Z in j)return a(Z,j[Z],X);return null}var f="[data-reactive-show-field], [data-reactive-show]";function Rj(j){try{let X=JSON.parse(j);if(X&&typeof X==="object"&&!Array.isArray(X))return X}catch{}return console.warn(`[phlex-reactive] malformed compound reactive_show payload ${JSON.stringify(j)} — skipped`),null}function b(j,X){if(!j||typeof j!=="object"||typeof j.field!=="string")return!1;let Z=X(j.field)??"";return r(j,Z)===!0}function t(j,X){if(!Array.isArray(j)||j.length===0)return null;return j.some((Z)=>Array.isArray(Z)&&Z.length>0&&Z.every(($)=>b($,X)))}function Dj(j,X){if(!j||typeof j!=="object")return null;let Z=j.any;if(Array.isArray(Z)&&(Z.length===0||Array.isArray(Z[0])))return t(Z,X);return Tj(j,X)}function Tj(j,X){let Z=Array.isArray(j.all)?"all":Array.isArray(j.any)?"any":null;if(!Z)return null;let $=j[Z];if($.length===0)return null;let J=$.map((Q)=>b(Q,X));return Z==="all"?J.every(Boolean):J.some(Boolean)}function Ij(j){if(typeof j==="string"&&i.test(j))return!0;return console.warn(`[phlex-reactive] refused cross-root show target ${JSON.stringify(j)} — skipped`),!1}var kj='a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';function Sj(j){return j.querySelectorAll?.(kj)?.[0]??null}function e(j){if(Array.isArray(j))return j;if(typeof j!=="string")return[];try{let X=JSON.parse(j);return Array.isArray(X)?X:[]}catch{return[]}}function jj(j,X){for(let Z of j){if(!Array.isArray(Z))continue;let[$,J={}]=Z;if(!Object.hasOwn(v,$)){console.warn(`[phlex-reactive] unknown client op ${JSON.stringify($)} — skipped`);continue}for(let Q of X(J))v[$](Q,J)}}function bj(j,X){let Z=j.to;if(X){if(Z==="@root")return[X];if(typeof Z!=="string"||Z==="")return[];if(j.global)return[...document.querySelectorAll(Z)];return[...X.querySelectorAll(Z)]}if(typeof Z!=="string"||Z===""||Z==="@root")return[];return[...document.querySelectorAll(Z)]}class sj extends Xj{static values={token:String};#h;#N=new Map;#q=new Map;#Yj;#F;#y;#C=0;#Y=new Map;#R=new WeakMap;#M=new Map;#v=new WeakSet;#U;#H;#W;#j;#$;#_;#f=!1;#D=0;#p=!1;#L;#A;connect(){if(E=!0,this.element.id==="")console.warn("[phlex-reactive] a reactive root has no id; its next-action token can't self-match "+"and may fall back to the first token in the response → a silent HTTP 403 on the NEXT action. "+"Put id: on the SAME element as reactive_attrs — use div(**reactive_root) (emits id + token together), "+"or div(id:, **reactive_attrs). The id: must NOT be on a child. See the README.");if(this.element.getAttribute?.("data-reactive-defer-token"))this.#u(),this.#A=()=>this.#u(),this.element.addEventListener?.("turbo:morph-element",this.#A);if(this.#Uj()){if(this.#U=()=>this.#I(),this.element.addEventListener?.("turbo:morph-element",this.#U),this.#I(),this.element.getAttribute?.("data-reactive-warn-unsaved")==="true")this.#kj()}if(this.#bj())this.#j=()=>this.#t(),this.element.addEventListener?.("input",this.#j),this.element.addEventListener?.("change",this.#j),this.element.addEventListener?.("turbo:morph-element",this.#j),this.#t();if(this.#P())this.#$=(j)=>{if(j?.type==="input"&&!this.#fj(j))return;this.#V()},this.element.addEventListener?.("input",this.#$),this.element.addEventListener?.("turbo:morph-element",this.#$),this.#V();if(this.#E())this.#_=()=>this.#b(),this.element.addEventListener?.("turbo:morph-element",this.#_),this.#b();if(this.#vj())this.#L=()=>this.recompute(),this.element.addEventListener?.("turbo:morph-element",this.#L),this.recompute()}#Uj(){if((this.element.getAttribute?.("data-action")??"").includes("reactive#trackDirty"))return!0;let j=this.element.querySelectorAll?.('[data-action*="reactive#trackDirty"]')??[];for(let X of j)if(this.#Z(X))return!0;return!1}disconnect(){if(this.#Bj(),this.#xj(),this.#Sj(),this.#yj(),this.#pj(),this.#oj(),this.#sj(),this.#A)this.element.removeEventListener?.("turbo:morph-element",this.#A)}#u(){let j=this.element;if(!j?.id)return;let X=j.getAttribute?.("data-reactive-defer-token");if(!X)return;if(j.getAttribute?.("data-reactive-defer-pending")!=="true")return;P(j.id,X)}dispatch(j){let{action:X,params:Z,debounce:$,throttle:J,confirm:Q,confirmWhen:G,outside:K,window:z,optimistic:Y}=j.params;if(!X)return;let H=j.params.busy??this.#$X(j.params.loading);if(K&&this.element.contains(j.target))return;let L=j.currentTarget??j.target;if(!z&&!this.#aj(Y,L))j.preventDefault();let W=this.#n(Q,G);if(!W)return this.#d(L,X,Z,$,J,Y,H);Promise.resolve().then(()=>w(W)).catch(()=>!1).then((_)=>{if(_)this.#d(L,X,Z,$,J,Y,H)})}runOps(j){let{ops:X,confirm:Z,confirmWhen:$,outside:J,window:Q}=j.params;if(J&&this.element.contains(j.target))return;if(!Q)j.preventDefault();let G=this.#n(Z,$);if(!G)return this.#Qj(this.#Jj(X));Promise.resolve().then(()=>w(G)).catch(()=>!1).then((K)=>{if(K)this.#Qj(this.#Jj(X))})}trackDirty(){this.#I()}recompute(j){if(j&&this.#v.has(j))return;let X=this.#Wj(),Z=X.map(([q])=>q),$=this.element.getAttribute?.("data-reactive-scope")||null,J=(q)=>$&&!q.includes("[")?`${$}[${q}]`:q,Q=this.#X(),G=new Map,K=(q)=>{if(G.has(q))return G.get(q);let U=null;for(let A of this.element.querySelectorAll(`[name="${J(q)}"]`))if(Q(A)){U=A;break}return G.set(q,U),U};for(let q of Z)this.#g(q,K(q)?.value??"");let z=this.element.getAttribute("data-reactive-compute-reducer-param"),Y=z?Zj(z):null;if(!Y){this.#c({},K);return}let H=this.#Hj("data-reactive-compute-outputs-param"),L={};for(let[q,U]of X){let A=K(q);if(U==="string")L[q]=A?.value??"";else{let V=Number(A?.value);L[q]=Number.isFinite(V)?V:0}}let W=Y(L,{changed:this.#_j(j,Z,$)})||{},_=[];for(let q of H){if(!(q in W))continue;let U=K(q);if(!U)continue;if(String(W[q])===U.value)continue;U.value=W[q],_.push(U)}for(let q of Object.keys(W)){let U=W[q];if(U===void 0||U===null)continue;this.#g(q,U)}this.#c(W,K);for(let q of _){let U=new Event("input",{bubbles:!0});this.#v.add(U),q.dispatchEvent(U)}}listnavNext(j){this.#m(j,1)}listnavPrev(j){this.#m(j,-1)}listnavPick(j){let X=this.#B(j),Z=X.findIndex(($)=>$.hasAttribute("data-reactive-highlighted"));if(Z<0)return;j.preventDefault(),X[Z].click()}listnavClose(j){for(let X of this.#B(j))X.removeAttribute("data-reactive-highlighted")}#m(j,X){let Z=this.#B(j);if(!Z.length)return;j.preventDefault();let $=Z.findIndex((G)=>G.hasAttribute("data-reactive-highlighted")),J=$<0?X>0?0:Z.length-1:($+X+Z.length)%Z.length;for(let G of Z)G.removeAttribute("data-reactive-highlighted");let Q=Z[J];Q.setAttribute("data-reactive-highlighted","true"),Q.scrollIntoView?.({block:"nearest"})}#B(j){let Z=(j?.currentTarget??j?.target??this.element).getAttribute?.("data-reactive-listnav-option-param")??this.element.getAttribute("data-reactive-listnav-option-param");if(!Z)return[];let $=this.#X();return Array.from(this.element.querySelectorAll(Z)).filter((J)=>!J.hidden&&$(J))}tagsAdd(j){if(!this.#E())return;if(j?.defaultPrevented)return;if(this.#B(j).some(($)=>$.hasAttribute?.("data-reactive-highlighted")))return;j?.preventDefault?.();let X=j?.currentTarget??j?.target;if(!X)return;if(!this.#Xj(String(X.value??"").split(",")))return;if(X.value="",this.#P())this.#V()}tagsPick(j){if(!this.#E())return;j?.preventDefault?.();let Z=(j?.currentTarget??j?.target)?.getAttribute?.("data-reactive-tag-param");if(!Z)return;if(!this.#Xj([Z]))return;let $=this.#uj();if(!$)return;$.value="",this.#V(),$.focus?.()}tagsRemove(j){if(!this.#E())return;j?.preventDefault?.();let Z=(j?.currentTarget??j?.target)?.getAttribute?.("data-reactive-tag-param");if(!Z)return;let $=this.#k();if(!$)return;let J=this.#S($),Q=J.filter((G)=>G.toLowerCase()!==Z.toLowerCase());if(Q.length===J.length)return;this.#Zj($,Q)}nestedAdd(j){j?.preventDefault?.();let Z=(j?.currentTarget??j?.target)?.getAttribute?.("data-reactive-association-param");if(!Z)return;if(typeof this.element?.querySelectorAll!=="function")return;let $=this.#X(),J=[...this.element.querySelectorAll(`[data-reactive-nested-list="${Z}"]`)].find($),G=[...this.element.querySelectorAll(`[data-reactive-nested-template="${Z}"]`)].find($)?.content?.firstElementChild;if(!J||!G){this.#lj(Z);return}let K=G.cloneNode(!0);this.#dj(K,this.#cj()),J.appendChild(K),[...K.querySelectorAll?.("input, select, textarea")??[]][0]?.focus?.()}nestedRemove(j){j?.preventDefault?.();let Z=(j?.currentTarget??j?.target)?.closest?.("[data-reactive-nested-row]");if(!Z)return;if(Z.closest?.('[data-controller~="reactive"]')!==this.element)return;let $=[...Z.querySelectorAll?.('input[name$="[_destroy]"]')??[]][0];if($){if($.value="1",typeof $.dispatchEvent==="function")$.dispatchEvent(new Event("input",{bubbles:!0}));Z.hidden=!0}else Z.parentNode?.removeChild?.(Z)}#Hj(j){let X=this.element.getAttribute(j);if(!X)return[];try{let Z=JSON.parse(X);return Array.isArray(Z)?Z:[]}catch{return[]}}#Wj(){let j=this.element.getAttribute("data-reactive-compute-inputs-param");if(!j)return[];try{let X=JSON.parse(j);if(Array.isArray(X))return X.map((Z)=>[Z,"number"]);if(X&&typeof X==="object")return Object.entries(X);return[]}catch{return[]}}#_j(j,X,Z){let $=j?.target;if(!$?.name||typeof $.closest!=="function")return null;let J=this.#Lj($.name,Z);if(!X.includes(J))return null;return this.#Z($)?J:null}#Lj(j,X){if(!X)return j;let Z=`${X}[`;return j.startsWith(Z)&&j.endsWith("]")?j.slice(Z.length,-1):j}#g(j,X){let Z=String(X);for(let $ of this.#Vj(j)){if($.textContent===Z)continue;$.textContent=Z}}#Vj(j){let X=this.element.querySelectorAll(`[data-reactive-text="${j}"]`);return Array.from(X).filter((Z)=>this.#Z(Z))}#c(j,X){let Z=this.#Nj();for(let[$,J]of Object.entries(Z)){let Q=$ in j?j[$]:X($)?.value;if(Q===void 0||Q===null)continue;let G=String(Q);for(let K of Array.isArray(J)?J:[J]){if(!Fj(K))continue;for(let z of document.querySelectorAll(K)){if(z.textContent===G)continue;z.textContent=G}}}}#Nj(){let j=this.element.getAttribute("data-reactive-compute-mirror-param");if(!j)return{};try{let X=JSON.parse(j);return X&&typeof X==="object"&&!Array.isArray(X)?X:{}}catch{return{}}}#d(j,X,Z,$,J,Q,G){if(this.#x("reactive:before-dispatch",{action:X,params:this.#$j(Z),element:this.element},{cancelable:!0}).defaultPrevented)return;let z=Number($)||0;if(z>0)return this.#Aj(j,z,X,Z,Q,G);let Y=Number(J)||0;if(Y>0)return this.#Oj(j,Y,X,Z,Q,G);return this.#O(X,Z,Q,j,G)}#O(j,X,Z,$,J){let Q=this.#rj(Z,$),G=this.#tj(j,$,J),K=this.#l()?this.#Mj(Z,$):null;return this.queue=(this.queue??Promise.resolve()).then(()=>this.#Dj(j,X,Q,G,K)),this.queue}#Mj(j,X){if(!j?.hide)return null;let Z=this.#Kj(j,X);if(!Z.length)return null;return()=>{let $=Z.filter((J)=>J.isConnected&&!J.hidden);if(!$.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.",$)}}#Aj(j,X,Z,$,J,Q){this.#T(j);let G=()=>{this.#T(j),this.#O(Z,$,J,j,Q)},K=setTimeout(G,X);j?.addEventListener?.("blur",G,{once:!0}),this.#N.set(j,{timer:K,flush:G})}#T(j){let X=this.#N.get(j);if(!X)return;clearTimeout(X.timer),j?.removeEventListener?.("blur",X.flush),this.#N.delete(j)}#Bj(){for(let j of[...this.#N.keys()])this.#T(j)}#Oj(j,X,Z,$,J,Q){let G=this.#q.get(j)??new Map;if(G.has(Z))return;let K=setTimeout(()=>{if(G.delete(Z),G.size===0)this.#q.delete(j)},X);return G.set(Z,K),this.#q.set(j,G),this.#O(Z,$,J,j,Q)}#xj(){for(let j of this.#q.values())for(let X of j.values())clearTimeout(X);this.#q.clear()}#x(j,X,{cancelable:Z=!1}={}){let $=new CustomEvent(j,{bubbles:!0,composed:!0,cancelable:Z,detail:X});return(this.element.isConnected?this.element:document).dispatchEvent($),$}#J(j,X,Z,$){let J=()=>{if(!this.element.isConnected){console.warn("[phlex-reactive] retry() ignored — the reactive root left the DOM");return}return this.#O(j,X)};this.#x("reactive:error",{action:j,params:Z,...$,retry:J})}#Q(j){if(this.element?.isConnected===!1)return;this.element?.setAttribute?.("data-reactive-error",j)}#Pj(){this.element?.removeAttribute?.("data-reactive-error")}#Ej(){let j=document.querySelector("[data-reactive-error-flash]");if(!j?.content)return;let X=j.getAttribute("data-reactive-error-flash")||"flash",Z=document.getElementById(X);if(!Z)return;Z.appendChild(j.content.cloneNode(!0))}#Fj(){if(typeof sessionStorage>"u")return Promise.resolve();let j=Number(sessionStorage.getItem(S));if(!Number.isFinite(j)||j<=0)return Promise.resolve();if(!O)O=!0,console.warn(`[phlex-reactive] latency simulator ACTIVE — every action is delayed by ${j}ms. Call PhlexReactive.disableLatencySim() (or clear sessionStorage) to turn it off.`);return new Promise((X)=>setTimeout(X,j))}#l(){return this.element?.getAttribute?.("data-reactive-debug")==="true"}#o(){return typeof performance<"u"&&typeof performance.now==="function"?performance.now():Date.now()}#Cj(j){if(!j)return[];let X=[],Z=/<turbo-stream\b([^>]*)>/g,$;while(($=Z.exec(j))!==null){let J=$[1],Q=J.match(/\baction="([^"]*)"/)?.[1]??"?",G=J.match(/\btarget="([^"]*)"/)?.[1];X.push(G?`${Q} → #${G}`:Q)}return X}#Rj(j){let{action:X,status:Z,ms:$}=j,Q=`reactive ${this.element?.id?`#${this.element.id} `:""}${X} → ${Z??"—"} (${Math.round($)}ms)`;if(console.groupCollapsed(Q),console.log(`params: [${j.paramNames.join(", ")}] + collected: [${j.fieldNames.join(", ")}]`),console.log(`encoding: ${j.encoding}`),j.streams.length)console.log(`streams: ${j.streams.join(" ")}`);console.log(`token: ${j.tokenRefreshed?"refreshed ✓":"unchanged"}`),console.groupEnd()}async#Dj(j,X,Z,$,J){let{fields:Q,files:G}=this.#a(),K=this.#$j(X),z={...Q,...K},Y=this.#G,H=G.length>0,L=H?this.#ij(Y,j,z,G):JSON.stringify({token:Y,act:j,params:z}),W=this.#l()?{action:j,paramNames:Object.keys(K),fieldNames:Object.keys(Q),encoding:H?"multipart":"json",status:null,streams:[],tokenRefreshed:!1,started:this.#o()}:null;await this.#Fj();try{if(navigator.onLine===!1){this.#z(Z),this.#Q("offline"),this.#J(j,X,z,{kind:"offline"});return}let _;try{let V={Accept:"text/vnd.turbo-stream.html","X-CSRF-Token":this.#GX()};if(!H)V["Content-Type"]="application/json";let B=this.#zX();if(B)V["X-Pgbus-Connection"]=B;_=await fetch(this.#JX(),{method:"POST",headers:V,body:L,credentials:"same-origin",signal:AbortSignal.timeout(this.#QX())})}catch(V){if(console.error("[phlex-reactive] action error",V),this.#z(Z),V?.name==="TimeoutError"||V?.name==="AbortError"){this.#Q("timeout"),this.#J(j,X,z,{kind:"timeout"});return}this.#Ej(),this.#Q("network"),this.#J(j,X,z,{kind:"network"});return}if(W)W.status=_.status;if(_.redirected){console.error("[phlex-reactive] action was redirected (auth/CSRF?) — no update applied"),this.#z(Z),this.#Q("redirected"),this.#J(j,X,z,{kind:"redirected",status:_.status});return}if(!_.ok){let V=await _.text();if(console.error(`[phlex-reactive] action failed: HTTP ${_.status}`,V),this.#z(Z),(_.headers.get("Content-Type")||"").includes("turbo-stream")){let B=this.#i(V);if(this.#G=B??this.#G,W)this.#s(W,V,B);window.Turbo.renderStreamMessage(V)}this.#Q("http"),this.#J(j,X,z,{kind:"http",status:_.status,body:V});return}let q=_.headers.get("Content-Type")||"";if(!q.includes("turbo-stream")){console.error(`[phlex-reactive] expected a turbo-stream, got "${q}" — no update applied`),this.#z(Z),this.#Q("content-type"),this.#J(j,X,z,{kind:"content-type",status:_.status});return}let U=await _.text(),A=this.#i(U);if(this.#G=A??this.#G,W)this.#s(W,U,A);if(window.Turbo.renderStreamMessage(U),J)queueMicrotask(J);this.#Pj(),this.#x("reactive:applied",{action:j,params:z,html:U})}catch(_){console.error("[phlex-reactive] action error",_),this.#z(Z),this.#x("reactive:error",{action:j,params:z,kind:"apply"})}finally{if($?.(),W)this.#Rj({...W,ms:this.#o()-W.started})}}#s(j,X,Z){j.streams=this.#Cj(X),j.tokenRefreshed=Z!=null}get#G(){return this.#h??this.tokenValue}set#G(j){this.#h=j}#i(j){let X=this.element.id;if(!X)return j.match(/data-reactive-token-value="([^"]+)"/)?.[1];let{token:Z,self:$}=this.#Tj(X),J=j.match(Z);if(J)return J[1];let Q=j.match($);if(Q)return Q[1].match(/data-reactive-token-value="([^"]+)"/)?.[1];return}#Tj(j){let X=this.#y;if(X&&X.id===j)return X;let Z=Bj(j);return this.#y={id:j,token:new RegExp(`<turbo-stream\\b[^>]*\\baction="reactive:token"[^>]*\\btarget="${Z}"[^>]*\\bdata-reactive-token-value="([^"]+)"`),self:new RegExp(`<turbo-stream\\b[^>]*\\baction="(?:replace|update)"[^>]*\\btarget="${Z}"[^>]*>([\\s\\S]*?)</turbo-stream>`)}}#Z(j){return j.closest('[data-controller~="reactive"]')===this.element}#X(){if(this.element.querySelectorAll('[data-controller~="reactive"]').length===0)return()=>!0;return(X)=>this.#Z(X)}#n(j,X){if(j)return j;if(!X)return null;let Z=X;if(typeof X==="string")try{Z=JSON.parse(X)}catch{return console.warn(`[phlex-reactive] malformed conditional confirm payload ${JSON.stringify(X)} — skipped`),null}if(!Z||typeof Z!=="object")return null;let{fields:$}=this.#a(),J=(G)=>$[G],Q;if(typeof Z.predicate==="string"){let G=$j(Z.predicate);if(!G)return console.warn(`[phlex-reactive] confirm predicate "${Z.predicate}" is not registered — proceeding without a dialog (register it with setConfirmPredicate)`),null;Q=!!G($)}else Q=t(Z.groups?.any,J)===!0;return Q?Z.message:null}#a(){let j={},X=[],Z=this.#X();return this.element.querySelectorAll("input[name], select[name], textarea[name]").forEach(($)=>{if(!Z($))return;if($.type==="file")for(let J of $.files??[])X.push({name:$.name,file:J,multiple:$.multiple});else if($.type==="checkbox")j[$.name]=$.checked;else if($.type==="radio"){if($.checked)j[$.name]=$.value}else j[$.name]=$.value}),this.element.querySelectorAll("[name]:is(lexxy-editor, trix-editor, [contenteditable=''], [contenteditable=true], [contenteditable=plaintext-only])").forEach(($)=>{if(!Z($))return;let J=$.getAttribute("name");if(!J)return;let Q=j[J];if(Q==null||Q==="")j[J]=$.value??$.textContent??$.innerHTML??""}),{fields:j,files:X}}#I(){if(typeof this.element?.querySelectorAll!=="function")return;let j=0;if(this.element.querySelectorAll("input[name], select[name], textarea[name]").forEach((X)=>{if(!this.#Z(X))return;if(X.type==="file")return;if(this.#Ij(X))X.setAttribute("data-reactive-dirty","true"),j++;else X.removeAttribute("data-reactive-dirty")}),j>0)this.element.setAttribute("data-reactive-dirty",String(j));else this.element.removeAttribute("data-reactive-dirty")}#Ij(j){if(j.type==="checkbox"||j.type==="radio")return j.checked!==j.defaultChecked;if(j.tag==="select"||j.options)return Array.from(j.options??[]).some((X)=>X.selected!==X.defaultSelected);return j.value!==j.defaultValue}#r(){let j=this.element.getAttribute?.("data-reactive-dirty"),X=Number(j);return Number.isFinite(X)&&X>0?X:0}#kj(){if(typeof window>"u"||typeof window.addEventListener!=="function")return;this.#H=(j)=>{if(this.#r()===0)return;return j.preventDefault(),j.returnValue="You have unsaved changes.",j.returnValue},this.#W=(j)=>{if(this.#r()===0)return;if(!(typeof window.confirm==="function"?window.confirm("You have unsaved changes. Leave anyway?"):!0))j.preventDefault?.()},window.addEventListener("beforeunload",this.#H),window.addEventListener("turbo:before-visit",this.#W)}#Sj(){if(this.#U)this.element.removeEventListener?.("turbo:morph-element",this.#U),this.#U=void 0;if(typeof window<"u"&&typeof window.removeEventListener==="function"){if(this.#H)window.removeEventListener("beforeunload",this.#H);if(this.#W)window.removeEventListener("turbo:before-visit",this.#W)}this.#H=void 0,this.#W=void 0}#bj(){if(this.element.getAttribute?.("data-reactive-show-targets"))return!0;let j=this.element.querySelectorAll?.(f)??[];for(let X of j)if(this.#Z(X))return!0;return!1}#t(){if(typeof this.element?.querySelectorAll!=="function")return;let j=this.#X(),X=this.element.getAttribute?.("data-reactive-scope")||null,Z=new Map,$=(J)=>{if(!Z.has(J))Z.set(J,this.#jj(J,j,X));return Z.get(J)};for(let J of this.element.querySelectorAll(f)){if(!j(J))continue;let Q=J.getAttribute("data-reactive-show");if(Q!==null){let Y=Dj(Rj(Q),$);if(Y!==null)this.#e(J,Y,j,X);continue}let G=J.getAttribute("data-reactive-show-field");if(!G)continue;let K=$(G);if(K===null)continue;let z=Cj(J,K);if(z===null)continue;this.#e(J,z,j,X)}this.#wj(j,Z,X)}#e(j,X,Z,$){if(j.hidden=!X,j.getAttribute("data-reactive-show-disable")!=="true")return;if(typeof j.querySelectorAll!=="function")return;for(let J of j.querySelectorAll("input[name], select[name], textarea[name]"))if(Z(J))J.disabled=!X;if(j.name&&Z(j))j.disabled=!X}#wj(j,X,Z){let $=this.#hj();for(let[J,Q]of Object.entries($)){if(!Q||typeof Q!=="object"||Array.isArray(Q))continue;if(!X.has(J))X.set(J,this.#jj(J,j,Z));let G=X.get(J);if(G===null)continue;let K=()=>G;for(let[z,Y]of Object.entries(Q)){if(!Ij(z))continue;let H;if(Array.isArray(Y)){if(Y.length===0){console.warn(`[phlex-reactive] malformed reactive_show_targets group for ${z} — skipped`);continue}H=Y.every((L)=>b(L,K))}else{let L=r(Y,G);if(L===null){console.warn(`[phlex-reactive] malformed reactive_show_targets predicate for ${z} — skipped`);continue}H=L}for(let L of document.querySelectorAll(z))L.hidden=!H}}}#hj(){let j=this.element.getAttribute?.("data-reactive-show-targets");if(!j)return{};try{let X=JSON.parse(j);if(X&&typeof X==="object"&&!Array.isArray(X))return X}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: { ... })"),{}}#jj(j,X,Z){let $=Z&&!j.includes("[")?`${Z}[${j}]`:j,J=!1,Q=null;for(let G of this.element.querySelectorAll(`[name="${$}"]`)){if(!X(G))continue;if(G.type==="checkbox")return G.checked?"true":"false";if(G.type==="radio"){if(G.checked)return G.value??"";J=!0;continue}Q??=G}if(Q)return Q.value??"";return J?"":null}#yj(){if(!this.#j)return;this.element.removeEventListener?.("input",this.#j),this.element.removeEventListener?.("change",this.#j),this.element.removeEventListener?.("turbo:morph-element",this.#j),this.#j=void 0}#P(){return!!(this.element.getAttribute?.("data-reactive-filter-input")&&this.element.getAttribute?.("data-reactive-filter-option"))}#vj(){return this.element.getAttribute?.("data-reactive-compute-seed")==="true"}#fj(j){let X=this.element.getAttribute("data-reactive-filter-input");return!!X&&typeof j.target?.matches==="function"&&j.target.matches(X)}#V(){if(typeof this.element?.querySelectorAll!=="function")return;let j=this.element.getAttribute("data-reactive-filter-input"),X=this.element.getAttribute("data-reactive-filter-option");if(!j||!X)return;let Z=this.#X(),$=[...this.element.querySelectorAll(j)].find(Z);if(!$)return;let J=($.value??"").trim().toLowerCase(),Q=0;for(let z of this.element.querySelectorAll(X)){if(!Z(z))continue;let Y=(z.getAttribute("data-reactive-filter-text")??z.textContent??"").toLowerCase(),H=z.hasAttribute?.("data-reactive-tags-selected")||J!==""&&!Y.includes(J);if(z.hidden=H,H)z.removeAttribute("data-reactive-highlighted");else Q++}let G=this.element.getAttribute("data-reactive-filter-group");if(G)for(let z of this.element.querySelectorAll(G)){if(!Z(z))continue;let Y=[...z.querySelectorAll(X)].filter(Z);if(Y.length===0)continue;z.hidden=Y.every((H)=>H.hidden)}let K=this.element.getAttribute("data-reactive-filter-empty");if(K){for(let z of this.element.querySelectorAll(K))if(Z(z))z.hidden=Q>0}}#pj(){if(!this.#$)return;this.element.removeEventListener?.("input",this.#$),this.element.removeEventListener?.("turbo:morph-element",this.#$),this.#$=void 0}#E(){return!!this.element.getAttribute?.("data-reactive-tags-field")}#k(){if(typeof this.element?.querySelectorAll!=="function")return null;let j=this.element.getAttribute("data-reactive-tags-field");if(!j)return null;let X=this.#X();return[...this.element.querySelectorAll(j)].find(X)??null}#S(j){let X=new Set,Z=[];for(let $ of String(j.value??"").split(",")){let J=$.trim();if(J===""||X.has(J.toLowerCase()))continue;X.add(J.toLowerCase()),Z.push(J)}return Z}#Xj(j){let X=this.#k();if(!X)return!1;let Z=this.#S(X),$=new Set(Z.map((Q)=>Q.toLowerCase())),J=!1;for(let Q of j){let G=String(Q??"").trim();if(G===""||$.has(G.toLowerCase()))continue;$.add(G.toLowerCase()),Z.push(G),J=!0}if(J)this.#Zj(X,Z);return J}#Zj(j,X){if(j.value=X.join(","),typeof j.dispatchEvent==="function")j.dispatchEvent(new Event("input",{bubbles:!0}));this.#b()}#uj(){if(typeof this.element?.querySelectorAll!=="function")return null;let j=this.element.getAttribute("data-reactive-filter-input");if(!j)return null;let X=this.#X();return[...this.element.querySelectorAll(j)].find(X)??null}#b(){let j=this.#k();if(!j)return;let X=this.#S(j),Z=this.#X();this.#mj(X,Z),this.#gj(X,Z)}#mj(j,X){let Z=[...this.element.querySelectorAll("[data-reactive-tags-list]")].find(X);if(!Z)return;let J=[...this.element.querySelectorAll("[data-reactive-tags-template]")].find(X)?.content?.firstElementChild;if(!J){if(!this.#f)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.#f=!0;return}while(Z.firstChild)Z.removeChild(Z.firstChild);for(let Q of j){let G=J.cloneNode(!0);G.setAttribute?.("data-reactive-tag",Q);let K=G.matches?.("[data-reactive-tag-text]")?G:(G.querySelectorAll?.("[data-reactive-tag-text]")??[])[0];if(K)K.textContent=Q;let z=[...G.querySelectorAll?.('[data-action*="reactive#tagsRemove"]')??[]];if(G.matches?.('[data-action*="reactive#tagsRemove"]'))z.push(G);for(let Y of z)Y.setAttribute?.("data-reactive-tag-param",Q);Z.appendChild(G)}}#gj(j,X){let Z=new Set(j.map(($)=>$.toLowerCase()));for(let $ of this.element.querySelectorAll("[role=option]")){if(!X($))continue;let J=$.getAttribute?.("data-reactive-tag-param");if(!J)continue;if(Z.has(J.toLowerCase()))$.setAttribute("data-reactive-tags-selected","true"),$.hidden=!0,$.removeAttribute?.("data-reactive-highlighted");else if($.hasAttribute?.("data-reactive-tags-selected")){if($.removeAttribute("data-reactive-tags-selected"),!this.#P())$.hidden=!1}}if(this.#P())this.#V()}#cj(){return this.#D=Math.max(this.#D+1,Date.now()),this.#D}#dj(j,X){let Z=[j,...j.querySelectorAll?.("*")??[]];for(let $ of Z)for(let J of["name","id","for"]){let Q=$.getAttribute?.(J);if(Q&&Q.includes("NEW_ROW"))$.setAttribute?.(J,Q.replaceAll("NEW_ROW",String(X)))}}#lj(j){if(this.#p)return;console.warn(`[phlex-reactive] nested rows: no owned [data-reactive-nested-list="${j}"] container + <template data-reactive-nested-template="${j}"> pair found in this root — the add `+"trigger did nothing. Render both inside the same reactive root (reactive_nested_list / reactive_nested_template)."),this.#p=!0}#oj(){if(!this.#_)return;this.element.removeEventListener?.("turbo:morph-element",this.#_),this.#_=void 0}#sj(){if(!this.#L)return;this.element.removeEventListener?.("turbo:morph-element",this.#L),this.#L=void 0}#ij(j,X,Z,$){let J=new FormData;J.append("token",j),J.append("act",X);for(let[G,K]of Object.entries(Z))this.#w(J,`params[${G}]`,K);let Q=this.#nj($);for(let{name:G,file:K,multiple:z}of $){let H=z||Q.has(G)?`params[${G}][]`:`params[${G}]`;J.append(H,K,K.name)}return J}#w(j,X,Z){if(Z==null)j.append(X,"");else if(Array.isArray(Z))Z.forEach(($,J)=>this.#w(j,`${X}[${J}]`,$));else if(typeof Z==="object")for(let[$,J]of Object.entries(Z))this.#w(j,`${X}[${$}]`,J);else j.append(X,String(Z))}#nj(j){let X=new Map;for(let{name:Z}of j)X.set(Z,(X.get(Z)??0)+1);return new Set([...X].filter(([,Z])=>Z>1).map(([Z])=>Z))}#$j(j){if(!j)return{};try{return typeof j==="string"?JSON.parse(j):j}catch{return{}}}#Jj(j){return e(j)}#Qj(j){jj(j,(X)=>this.#Gj(X))}#Gj(j){let X=j.to;if(X==="@root")return[this.element];if(typeof X!=="string"||X==="")return[];if(j.global)return[...document.querySelectorAll(X)];return[...this.element.querySelectorAll(X)].filter((Z)=>this.#Z(Z))}#aj(j,X){if(j?.checked!=="keep")return!1;let Z=X?.type;return Z==="checkbox"||Z==="radio"}#rj(j,X){if(!j)return null;let Z=this.#zj(j,X,!0);return Z.length?Z:null}#z(j){if(!j)return;if(!this.element.isConnected)return;for(let X of j)X()}#tj(j,X,Z){this.#jX(j,X);let $=Z?this.#zj(Z,X,!1):[];l();let J=!1;return()=>{if(J)return;J=!0,this.#XX(j,X),o();for(let Q of $)Q()}}#zj(j,X,Z){let $=[];for(let J of this.#Kj(j,X)){if(j.add_class){let Q=j.add_class.filter((G)=>!J.classList.contains(G));if(J.classList.add(...Q),Q.length)$.push(()=>J.classList.remove(...Q))}if(j.remove_class){let Q=j.remove_class.filter((G)=>J.classList.contains(G));if(J.classList.remove(...Q),Q.length)$.push(()=>J.classList.add(...Q))}if(j.toggle_class)j.toggle_class.forEach((Q)=>J.classList.toggle(Q)),$.push(()=>j.toggle_class.forEach((Q)=>J.classList.toggle(Q)));if(j.hide)J.hidden=!0,$.push(()=>J.hidden=!1);if(j.show)J.hidden=!1,$.push(()=>J.hidden=!0)}if(X&&(j.disable||j.text!=null))$.push(this.#ej(j,X));if(Z&&j.checked==="keep"&&X&&"checked"in X){let J=X.checked;$.push(()=>X.checked=!J)}return $}#Kj(j,X){if(j.to==null)return X?[X]:[];return this.#Gj({to:j.to})}#ej(j,X){let Z=this.#M.get(X);if(Z)Z.count++;else this.#M.set(X,{count:1,disabled:X.disabled,html:X.innerHTML,hadText:j.text!=null,swappedTo:j.text});if(j.disable)X.disabled=!0;if(j.text!=null)X.innerHTML=j.text;return()=>this.#ZX(X,j)}#jX(j,X){if(this.#K(X,j,1),this.#K(this.element,j,1),this.#Y.set(j,(this.#Y.get(j)??0)+1),this.#C++===0)this.element.setAttribute("aria-busy","true");for(let Z of this.#qj(j))this.#K(Z,j,1)}#XX(j,X){this.#K(X,j,-1),this.#K(this.element,j,-1);let Z=(this.#Y.get(j)??1)-1;if(Z<=0)this.#Y.delete(j);else this.#Y.set(j,Z);if(--this.#C<=0)this.#C=0,this.element.removeAttribute("aria-busy");for(let $ of this.#qj(j))this.#K($,j,-1)}#K(j,X,Z){if(!j||typeof j.getAttribute!=="function")return;let $=this.#R.get(j)??new Map,J=($.get(X)??0)+Z;if(J<=0)$.delete(X);else $.set(X,J);if($.size===0){this.#R.delete(j),j.removeAttribute("data-reactive-busy");return}this.#R.set(j,$),j.setAttribute("data-reactive-busy",[...$.keys()].join(" "))}#qj(j){return[...this.element.querySelectorAll?.("[data-reactive-busy-on]")??[]].filter((Z)=>Z.getAttribute("data-reactive-busy-on")===j&&this.#Z(Z))}#ZX(j,X){let Z=this.#M.get(j);if(!Z)return;if(--Z.count>0)return;if(this.#M.delete(j),!j.isConnected)return;if(X.disable)j.disabled=Z.disabled;if(Z.hadText&&j.innerHTML===Z.swappedTo)j.innerHTML=Z.html}#$X(j){if(!j||typeof j!=="object")return null;let{class:X,...Z}=j;return X==null?j:{...Z,add_class:X}}#JX(){return this.#Yj??=document.querySelector('meta[name="phlex-reactive-action-path"]')?.content||"/reactive/actions"}#QX(){if(this.#F!=null)return this.#F;let j=document.querySelector('meta[name="phlex-reactive-timeout"]')?.content,X=Number(j);return this.#F=Number.isFinite(X)&&X>0?X:30000}#GX(){return document.querySelector('meta[name="csrf-token"]')?.content??""}#zX(){return document.querySelector("pgbus-stream-source[connection-id]")?.getAttribute("connection-id")||document.querySelector('meta[name="pgbus-connection-id"]')?.content||null}}export{fj as resetReactiveDefers,dj as resetReactiveActivity,Jj as registerReactiveVisit,Qj as registerReactiveToken,Vj as registerReactiveOffline,Gj as registerReactiveJs,_j as registerReactiveDismiss,zj as registerReactiveDefer,y as registerReactiveActions,cj as reactiveActivityCount,pj as pendingDeferVia,o as exitReactiveActivity,Bj as escapeRegExp,l as enterReactiveActivity,Nj as enableLatencySim,Mj as disableLatencySim,sj as default,Oj as checkReactiveRegistration,lj as __resetReactiveRegistrationForTest,mj as __resetReactiveOfflineForTest,gj as __resetReactiveLatencyForTest,uj as __resetReactiveDismissForTest,oj as __markReactiveConnectedForTest,S as LATENCY_KEY,d as ACTIVE_ATTR};
|
|
2
2
|
|
|
3
|
-
//# debugId=
|
|
3
|
+
//# debugId=C019D09F55CE970764756E2164756E21
|
|
4
4
|
//# sourceMappingURL=reactive_controller.min.js.map
|