phlex-reactive 0.12.0 → 0.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 27841384eb24e3e9d66f404b495705b2db4c30fec683ad21ae0f2b2c258c6a00
4
- data.tar.gz: 14990c96dd4dfafefd56794531cc116f8a17a9bc6aa76d8caf50679fb95abc1b
3
+ metadata.gz: 753ece18695afa9629ee3bec0fd51722f83618adcb2488265a873ff93bf89dd0
4
+ data.tar.gz: 55fd2590040f0a9c0831e622981d50b71145fbcb289157237787c59b13ec6426
5
5
  SHA512:
6
- metadata.gz: c36de342a6909b7cbd04f58a9d6da9b77497acfe5f6777c200932a54af5251872ec91b2a5d624f14f1cb5b3faed8fe60e5a169e4363cb6d32bf7d24418fd88ac
7
- data.tar.gz: 45c811958371300f558d57f41e8ead04e607cc07cd43a9915aa2534fa15b650d1c284ca92b871734b755791b730fdb325f7e28e7f8372616aff383887873f340
6
+ metadata.gz: 8ba6bd8917f55a95d2039f8f86ac393c23c564da694575bb1978b0eeb5f1d5667a0531b0ac0c2a4f8f8e23e03199461eeba0d2dc3decb866b377b404ddb44665
7
+ data.tar.gz: 4f46e011c63a7500e6262fb3b24fceaf4c13c64b99d1555c0d7fe130326791ba388245c4493a8716231f5510f6d336e91492985a193bfaa5851c9475f3b4b173
data/CHANGELOG.md CHANGED
@@ -8,6 +8,26 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
8
 
9
9
  ### Added
10
10
 
11
+ - **Instance-dynamic wire names — keyword escape hatches on the
12
+ field-compiling helpers (#224).** A form builder's wire name is computed per
13
+ instance (`user[tags]`), which the class-level `reactive_scope` compile can't
14
+ express — the gap that forced phlex-forms' `tag_field` draft
15
+ (mhenrixon/phlex-forms#6) onto raw data attrs. Every helper that compiles a
16
+ field name now takes a verbatim keyword escape hatch (never re-scoped,
17
+ validated at render, mutually exclusive with the blessed field form):
18
+ `reactive_tags(name: "user[tags]")`, `reactive_filter(input: "#tags_query")`
19
+ (a raw CSS selector — the deliberately **name-less** query input inside a
20
+ real form, targeted by id, so it can never submit a stray param),
21
+ `nested_field_name(:items, :qty, scope: "order")` (a per-call parent prefix
22
+ that wins over `reactive_scope`), and
23
+ `reactive_nested_list(:items, as: :json, name: "order[items]")` (the hidden
24
+ JSON sync field, JSON mode only). Server-side sugar only — the client already
25
+ resolves these attributes as arbitrary root-scoped selectors; the shipped
26
+ wire for existing calls is byte-identical. Note: `reactive_filter(input:)`
27
+ deliberately re-blesses the kwarg removed in #186 — the field form stays the
28
+ blessed default, and a pre-0.10 `input:`/`option:` call shape is valid again
29
+ with identical semantics instead of raising the removal error.
30
+
11
31
  - **Project board — the kanban flagship demo (#216).** A live three-lane board
12
32
  on the docs site (`/docs/example-project-board`, `/demos/project-board`)
13
33
  composing the toolkit end to end: each card is its own nested reactive root
@@ -220,6 +240,25 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
220
240
 
221
241
  ### Fixed
222
242
 
243
+ - **`reactive_nested_remove(confirm:)` now interpolates `%{field}` on client-added
244
+ rows (#222).** A row added in the browser via `reactive_nested_add` is a
245
+ `cloneNode` of the `<template>`, and the clone path (`#renumberNestedRow` /
246
+ `#seedNestedRow`) never rewrote the confirm attribute — so a per-row confirm
247
+ froze to the template's value-less string on every added row (the exact rows
248
+ the primitive exists for). The client now resolves `%{field}` placeholders in
249
+ the confirm message from **that row's live field values** at click time (keyed
250
+ by the same trailing-bracket inference `as: :json` uses), so
251
+ `confirm: "Delete '%{name}'?"` on the template shows `Delete 'Widget'?` on the
252
+ added row — reflecting a later edit too (resolved on remove, not on clone). An
253
+ unresolved `%{key}` is left as its literal text (debuggable, never a silent
254
+ blank); the placeholder works in the conditional Hash's `message:` as well.
255
+ Server-rendered rows already interpolate server-side, so their finished strings
256
+ are unaffected. **Also (superset of the issue's proposal 3):** `confirmResolver`
257
+ now receives an optional second argument — a context object, always `{ el }`
258
+ (the trigger), plus `{ row, fields }` on a `reactive_nested_remove` — so a
259
+ themed-dialog override can build row-specific messages programmatically. The
260
+ arg is additive: a one-parameter resolver (and `window.confirm`) is unchanged.
261
+
223
262
  - **A draft (unsaved-parent) token can now round-trip real server actions (#208).**
224
263
  `Component::Identity` already signed a gid-less `{c, state}` token for an
225
264
  unpersisted (or nil) record, but `from_identity` still `fetch`ed the absent
data/README.md CHANGED
@@ -390,17 +390,17 @@ Use in controllers: `render turbo_stream: Counter.replace(counter)`.
390
390
  | `reactive_text(:name, initial)` | Mirror a compute output (or a declared input) into a **text node** — a live preview heading, a character counter, `"Hello, {name}"` — via `textContent` (XSS-safe). The text sibling of `reactive_field`; carries no `name`, so it's never POSTed. See [Client-side computes](#client-side-computes-reactive_compute--reactive_text). |
391
391
  | `reactive_show(if:/if_any:/unless:)` | **Value-conditional visibility** (the `x-show`/`data-show` case): spread onto the element to show/hide — it toggles `hidden` from the fields' **current values**, client-only, zero round trip. One conditions language: a **Hash is an AND**, an **Array is membership**, a **Range is a threshold**, `if_any:` is OR-of-AND, `unless:` negates. `reactive_values` computes first paint; `disable:` disables a hidden section's controls. See [Value-conditional visibility](#value-conditional-visibility-reactive_show). |
392
392
  | `reactive_show_targets(:field, "#id" => value)` | **Cross-root visibility**: the component that owns the field declares which **outside**, id-allowlisted elements it governs (a nav tab, a panel in another pane) — the visibility parallel of `mirror:`. Spread on the **root** via `mix(reactive_root, …)`, **once per root** — several fields go in one call via the hash form. The value uses the same `where`-style vocabulary (`"advanced"`, `%w[a b]`, `10..`); a `"#id"` **key** takes a full conditions Hash for a **multi-field** predicate (`"#warn" => { if: { type: "trade", price: ..0 } }`). Id selectors only (raise at render + client warn-skip); toggles `hidden` only. See [Value-conditional visibility](#value-conditional-visibility-reactive_show). |
393
- | `reactive_filter(:field, option: nil, group: nil, empty: nil)` | **Client-side option filtering** for a preloaded combobox: spread onto the root and name the **field** that drives it — `reactive_filter(:q)` compiles `:q` to `[name="q"]` (scope-aware) and typing shows/hides the options by their `data-reactive-filter-text` haystack, **zero round trips**. `option:` defaults to `[role=option]`; optional `group:` collapses an all-hidden group header; `empty:` reveals a no-matches node. See [Client-side option filtering](#client-side-option-filtering-reactive_filter). |
393
+ | `reactive_filter(:field, option: nil, group: nil, empty: nil)` | **Client-side option filtering** for a preloaded combobox: spread onto the root and name the **field** that drives it — `reactive_filter(:q)` compiles `:q` to `[name="q"]` (scope-aware) and typing shows/hides the options by their `data-reactive-filter-text` haystack, **zero round trips**. `option:` defaults to `[role=option]`; optional `group:` collapses an all-hidden group header; `empty:` reveals a no-matches node. `input:` is the escape hatch — a raw CSS selector for a **name-less** driving input (`input: "#tags_query"`), the form-builder case. See [Client-side option filtering](#client-side-option-filtering-reactive_filter). |
394
394
  | `reactive_listnav("[role=option]")` | The **standalone** combobox keyboard wiring (Arrow/Enter/Escape) for an input that fires **no action** — the preload-and-filter case. Same behavior as `on(…, listnav:)`, minus the POST. |
395
- | `reactive_tags(:tags)` | **Tag-chip input** (the combobox/tags widget): spread onto the root and name the hidden field that stores the **comma-joined** value — the client maintains that field + the chip list entirely client-side (form state, zero round trips), rebuilding chips from your server-owned `<template>`. Composes with `reactive_filter` (type to narrow) and `reactive_listnav` (Enter picks the highlighted option). See [Tag-chip input](#tag-chip-input-reactive_tags). |
395
+ | `reactive_tags(:tags)` | **Tag-chip input** (the combobox/tags widget): spread onto the root and name the hidden field that stores the **comma-joined** value — the client maintains that field + the chip list entirely client-side (form state, zero round trips), rebuilding chips from your server-owned `<template>`. Composes with `reactive_filter` (type to narrow) and `reactive_listnav` (Enter picks the highlighted option). `name:` is the escape hatch — a **verbatim** wire name (`name: "user[tags]"`, never re-scoped), the form-builder case. See [Tag-chip input](#tag-chip-input-reactive_tags). |
396
396
  | `reactive_tags_add` / `reactive_tags_option(tag)` / `reactive_tags_remove(tag)` | The tags triggers, all **client-only**: `reactive_tags_add` on the query input adds the typed text on Enter (mix it **after** `reactive_listnav`; Enter never submits the enclosing form); `reactive_tags_option` makes a preloaded suggestion add its declared tag on click; `reactive_tags_remove` is a chip's remove button (no arg inside the template — the client fills the tag per chip). |
397
397
  | `reactive_compute :name, inputs: { title: :string, qty: :number }, outputs:` | **Typed** inputs: a `:string` reaches the JS reducer raw, a `:number` is coerced through `Number`. The array form (`inputs: %i[a b]`) stays all-numeric; the **permit-style** form (`inputs: [:qty, title: :string]`) mixes both — bare symbols default `:number`, a trailing hash types the exceptions. `outputs:` is the field allowlist; a reducer-result key also paints any owned `reactive_text` node by presence and any `mirror:` id, so an `outputs:` entry that exists only to reach a text node is redundant (harmless — a widening). |
398
398
  | `reactive_compute :name, ..., mirror: { sum: "#summary-sum" }` | **Cross-root text mirrors**: paint a compute value into declared, id-allowlisted nodes **outside** the reactive root (a recap in another tab pane) via `textContent` — no bespoke listener. See [Cross-root mirrors](#cross-root-mirrors-mirror--painting-a-recap-outside-the-root). |
399
399
  | `reactive_dirty` / `reactive_dirty warn_unsaved: true` / `reactive_dirty only: %i[...]` | **Dirty tracking**, declared once at the class level, against the DOM's own `defaultValue`/`defaultChecked`/`defaultSelected` — no client state. Marks changed fields + the root `data-reactive-dirty`; `warn_unsaved:` arms a `beforeunload`/`turbo:before-visit` guard; `only:` scopes tracking to named fields. Style with `[data-reactive-dirty]`. See [Dirty-field tracking](#dirty-field-tracking-reactive_dirty). |
400
400
  | `nested_update!(:assoc, attrs)` | Map a nested param onto `<assoc>_attributes` with id preservation; update the record. |
401
- | `reactive_nested_list(:assoc, as: :attributes \| :json)` / `reactive_nested_template(:assoc)` / `reactive_nested_row` | **Draft nested-attribute rows** (the "new parent + child rows" form): the container rows land in, the `<template>` holding ONE row's markup, and the row wrapper marker — all **client-only** form state, keyed by association (several collections per root). `as: :json` (default `:attributes`) serializes the rows into ONE hidden JSON field instead of posting `accepts_nested_attributes_for` names — for an app whose controller `JSON.parse`s a serialized param. See [Draft rows for a new parent](#draft-rows-for-a-new-parent-reactive_nested_). |
401
+ | `reactive_nested_list(:assoc, as: :attributes \| :json)` / `reactive_nested_template(:assoc)` / `reactive_nested_row` | **Draft nested-attribute rows** (the "new parent + child rows" form): the container rows land in, the `<template>` holding ONE row's markup, and the row wrapper marker — all **client-only** form state, keyed by association (several collections per root). `as: :json` (default `:attributes`) serializes the rows into ONE hidden JSON field instead of posting `accepts_nested_attributes_for` names — for an app whose controller `JSON.parse`s a serialized param; `name:` names that hidden field **verbatim** (`name: "order[todos]"`, never re-scoped — the form-builder escape hatch, JSON mode only). See [Draft rows for a new parent](#draft-rows-for-a-new-parent-reactive_nested_). |
402
402
  | `reactive_nested_add(:assoc, from:, clear:)` / `reactive_nested_remove(confirm:)` | The row triggers, client-only (zero round trips): add clones the template and renumbers its placeholder index; remove deletes a draft row from the DOM, or `_destroy`-marks + hides a persisted row (a hidden `[_destroy]` input present). **Fill-then-add**: `from: { row_field => "#source-selector" }` seeds the new row from add controls that live OUTSIDE it (a preset select, a typeahead), and `clear: true` resets them — composes with `as: :attributes` AND `as: :json`. **Confirm on remove**: `reactive_nested_remove(confirm: "Really delete this row?")` gates the remove behind the same overridable `confirmResolver` as `on`/`on_client` (a per-row string, or the conditional `{ when:, message: }` Hash). See [Draft rows for a new parent](#draft-rows-for-a-new-parent-reactive_nested_). |
403
- | `nested_field_name(:assoc, :field, index: nil)` | The Rails `accepts_nested_attributes_for` wire name for one row field — `order[line_items_attributes][NEW_ROW][quantity]` (the template placeholder) by default, a real index when given. Scope-aware under `reactive_scope`. |
403
+ | `nested_field_name(:assoc, :field, index: nil)` | The Rails `accepts_nested_attributes_for` wire name for one row field — `order[line_items_attributes][NEW_ROW][quantity]` (the template placeholder) by default, a real index when given. Scope-aware under `reactive_scope`; `scope:` overrides it **per call** (`scope: "order"`, verbatim — the form-builder escape hatch). |
404
404
  | `reactive_collection :name, item:, container:, count:, empty:, size:` | Declare an add/remove-row list once; actions call `reply.append`/`prepend`/`remove`. See [Reactive collections](#reactive-collections-addremove-rows--count--empty-state). |
405
405
  | `reply.replace` / `.morph` / `.update` / `.remove` / `.redirect(url)` / `.with(*)` / `.js(ops)` | Return from an action to control the reply (flash, remove, redirect, multi-stream, server-pushed client ops). See [Controlling the action's reply](#reply--controlling-the-actions-reply). |
406
406
  | `reply.append(name, model)` / `.prepend(...)` / `.remove(name, model)` | Add/remove a row in a declared `reactive_collection` (row + count + empty-state in one reply). |
@@ -1218,6 +1218,17 @@ stays its own signed `on(:select)` trigger. Only *filtering* is client-side —
1218
1218
  selection still round-trips as a real signed action. Blank selectors raise at
1219
1219
  render: a dead binding must fail loudly, not no-op in the browser.
1220
1220
 
1221
+ **The escape hatch — a name-less input, targeted by id.** Inside a real
1222
+ POST/GET form, a *named* query input submits a stray param alongside your
1223
+ value. A form builder (phlex-forms' `tag_field`) therefore renders the query
1224
+ input with an **id and no `name`** — which the field form can't express. Pass
1225
+ `input:` (a raw CSS selector, verbatim — never re-scoped) instead of the field;
1226
+ exactly one of the two per call:
1227
+
1228
+ ```ruby
1229
+ reactive_filter(input: "#user_tags_query") # the input carries id, NO name
1230
+ ```
1231
+
1221
1232
  ### Tag-chip input (`reactive_tags`)
1222
1233
 
1223
1234
  The composed combobox/tags widget: preload suggestions, type to narrow, Enter or
@@ -1272,7 +1283,23 @@ dispatches a real `input` event on the hidden field, so `reactive_dirty`,
1272
1283
 
1273
1284
  The styled, form-builder-integrated version of this widget (label/errors/
1274
1285
  theming) belongs in your form layer — these helpers are deliberately the
1275
- unstyled primitives, like `reactive_filter` before them.
1286
+ unstyled primitives, like `reactive_filter` before them. That form layer is
1287
+ exactly who needs the **escape hatches**: a form builder's wire name is
1288
+ computed **per instance** (`user[tags]`, from the builder's object name), which
1289
+ the class-level `reactive_scope` compile can't express. `name:` takes the wire
1290
+ name **verbatim** (never re-scoped), and the query input goes name-less,
1291
+ targeted by id via `reactive_filter(input:)` — so a real form submit carries
1292
+ `user[tags]` and nothing else:
1293
+
1294
+ ```ruby
1295
+ div(**mix(reactive_root(id: "user_tags_widget"),
1296
+ reactive_tags(name: "user[tags]"), # verbatim — never re-scoped
1297
+ reactive_filter(input: "#user_tags_query"))) do # id-targeted, name-less input
1298
+ input(type: :hidden, name: "user[tags]", id: "user_tags", value: @tags.join(","))
1299
+ # …chips/template/suggestions as above…
1300
+ input(id: "user_tags_query", type: "search", **mix(reactive_listnav, reactive_tags_add))
1301
+ end
1302
+ ```
1276
1303
 
1277
1304
  ### Draft rows for a new parent (`reactive_nested_*`)
1278
1305
 
@@ -1346,6 +1373,12 @@ it on save. Several collections can share one root (everything is keyed by the
1346
1373
  association name); nesting a collection inside another's template is not
1347
1374
  supported.
1348
1375
 
1376
+ `nested_field_name` is scope-aware (`reactive_scope :order` → the
1377
+ `order[…]` wrap above). When the parent prefix is **per-instance** — a form
1378
+ builder's object name, possibly itself bracketed (`"user[profile]"`) — pass
1379
+ `scope:` per call instead; it's used verbatim and wins over the class-level
1380
+ scope: `nested_field_name(:line_items, :quantity, scope: "order")`.
1381
+
1349
1382
  Two boundaries to respect: the DOM is the single source of truth for unsent
1350
1383
  draft rows, so a **server re-render of the root replaces them** — keep
1351
1384
  replace-shaped actions out of a root holding unsent rows. And once the parent
@@ -1363,6 +1396,9 @@ path exactly as it is:
1363
1396
  div(**reactive_nested_list(:line_items, as: :json)) { } # + a hidden field to sync
1364
1397
  # the hidden field the client keeps in sync (seed "[]" so an empty submit posts one):
1365
1398
  input(type: "hidden", **reactive_field(:line_items), value: "[]")
1399
+
1400
+ # Form-builder escape hatch: name the hidden field VERBATIM (never re-scoped):
1401
+ div(**reactive_nested_list(:line_items, as: :json, name: "order[line_items]")) { }
1366
1402
  ```
1367
1403
 
1368
1404
  ```ruby
@@ -1512,9 +1548,27 @@ the user dismissed the dialog) cancels the action, exactly like declining the
1512
1548
  native prompt. The native default is always prevented up front, so a `submit`
1513
1549
  trigger never navigates while the dialog is open.
1514
1550
 
1515
- Unset, behavior is identical to the native `window.confirm`the `confirm:`
1516
- markup and `on(...)` API are unchanged; only the client's resolution strategy
1517
- gains a seam.
1551
+ The resolver also gets an **optional second argument**a context object — so a
1552
+ power-user override can build the string itself instead of relying on the message
1553
+ alone. It always carries `{ el }` (the trigger element the confirm fired from);
1554
+ on a `reactive_nested_remove` it additionally carries `{ row, fields }` (the row
1555
+ element and its `{ key => value }` field map), so a themed dialog can render
1556
+ row-specific detail programmatically:
1557
+
1558
+ ```js
1559
+ setConfirmResolver((message, ctx) => {
1560
+ // message is already interpolated (client-added rows resolve %{field}, see below)
1561
+ return myThemedDialog(message, { trigger: ctx.el, fields: ctx.fields })
1562
+ })
1563
+ ```
1564
+
1565
+ The second argument is purely additive — a one-parameter resolver keeps working
1566
+ untouched. Unset, behavior is identical to the native `window.confirm`; the
1567
+ `confirm:` markup and `on(...)` API are unchanged. For per-row confirm messages
1568
+ on **client-added** draft rows, the message the resolver receives is already
1569
+ interpolated from the row's live field values (`confirm: "Delete '%{name}'?"` →
1570
+ `Delete 'Widget'?`) — see [Draft rows for a new
1571
+ parent](#draft-rows-for-a-new-parent-reactive_nested_).
1518
1572
 
1519
1573
  ### `reply` — controlling the action's reply
1520
1574
 
@@ -20,6 +20,14 @@
20
20
  // Promise.resolve, so a bare boolean and a Promise<boolean> both work. It must
21
21
  // resolve truthy to proceed; a falsy resolve (or a rejection) cancels the
22
22
  // action — exactly like declining the native prompt.
23
+ //
24
+ // The resolver receives an OPTIONAL 2nd arg (issue #222): a context object,
25
+ // always `{ el }` (the trigger element), plus `{ row, fields }` on a
26
+ // reactive_nested_remove (the row element + its { key: value } field map). It's
27
+ // purely additive — a one-arg resolver (and window.confirm, which ignores extra
28
+ // args) keeps working unchanged. On a client-added draft row the `message` the
29
+ // resolver receives is already interpolated from the row's live field values
30
+ // (`confirm: "Delete '%{name}'?"` → "Delete 'Widget'?").
23
31
 
24
32
  // The default: wrap the synchronous native confirm in a Promise so the call
25
33
  // site can always `await` it. Read window lazily (per call), not at module load
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["confirm.js"],
4
4
  "sourcesContent": [
5
- "// The overridable confirmation resolver behind `on(:action, confirm: \"…\")`\n// (issue #55, follow-up to #52).\n//\n// The reactive controller preempts the click event (its own preventDefault +\n// POST), so Hotwire's `data-turbo-confirm` — which routes through\n// `Turbo.config.forms.confirm` — never runs for a reactive trigger. That made\n// the reactive path the ONE interaction a Hotwire app couldn't theme: every\n// confirmable reactive action showed the unstyled native window.confirm chrome.\n//\n// This module is the seam. `dispatch` resolves the confirm message through\n// `confirmResolver`, which defaults to a Promise-wrapped window.confirm (so the\n// 0.4.5 behavior is byte-for-byte unchanged when nothing is configured: sync,\n// no dependency, screen-reader friendly). An app reuses its themed dialog with\n// one line at boot:\n//\n// import { setConfirmResolver } from \"phlex/reactive/confirm\"\n// setConfirmResolver((message) => window.Turbo.config.forms.confirm(message))\n//\n// The resolver may be sync or async — the controller awaits it via\n// Promise.resolve, so a bare boolean and a Promise<boolean> both work. It must\n// resolve truthy to proceed; a falsy resolve (or a rejection) cancels the\n// action — exactly like declining the native prompt.\n\n// The default: wrap the synchronous native confirm in a Promise so the call\n// site can always `await` it. Read window lazily (per call), not at module load\n// — under SSR / test the global may not exist yet when this module is imported.\nexport let confirmResolver = (message) =>\n Promise.resolve(typeof window !== \"undefined\" ? window.confirm(message) : true)\n\n// Override the resolver. Pass a function `(message) => boolean | Promise<boolean>`.\n// Truthy resolves the action through; falsy (or a rejected Promise) cancels it.\nexport function setConfirmResolver(fn) {\n confirmResolver = fn\n}\n"
5
+ "// The overridable confirmation resolver behind `on(:action, confirm: \"…\")`\n// (issue #55, follow-up to #52).\n//\n// The reactive controller preempts the click event (its own preventDefault +\n// POST), so Hotwire's `data-turbo-confirm` — which routes through\n// `Turbo.config.forms.confirm` — never runs for a reactive trigger. That made\n// the reactive path the ONE interaction a Hotwire app couldn't theme: every\n// confirmable reactive action showed the unstyled native window.confirm chrome.\n//\n// This module is the seam. `dispatch` resolves the confirm message through\n// `confirmResolver`, which defaults to a Promise-wrapped window.confirm (so the\n// 0.4.5 behavior is byte-for-byte unchanged when nothing is configured: sync,\n// no dependency, screen-reader friendly). An app reuses its themed dialog with\n// one line at boot:\n//\n// import { setConfirmResolver } from \"phlex/reactive/confirm\"\n// setConfirmResolver((message) => window.Turbo.config.forms.confirm(message))\n//\n// The resolver may be sync or async — the controller awaits it via\n// Promise.resolve, so a bare boolean and a Promise<boolean> both work. It must\n// resolve truthy to proceed; a falsy resolve (or a rejection) cancels the\n// action — exactly like declining the native prompt.\n//\n// The resolver receives an OPTIONAL 2nd arg (issue #222): a context object,\n// always `{ el }` (the trigger element), plus `{ row, fields }` on a\n// reactive_nested_remove (the row element + its { key: value } field map). It's\n// purely additive — a one-arg resolver (and window.confirm, which ignores extra\n// args) keeps working unchanged. On a client-added draft row the `message` the\n// resolver receives is already interpolated from the row's live field values\n// (`confirm: \"Delete '%{name}'?\"` → \"Delete 'Widget'?\").\n\n// The default: wrap the synchronous native confirm in a Promise so the call\n// site can always `await` it. Read window lazily (per call), not at module load\n// — under SSR / test the global may not exist yet when this module is imported.\nexport let confirmResolver = (message) =>\n Promise.resolve(typeof window !== \"undefined\" ? window.confirm(message) : true)\n\n// Override the resolver. Pass a function `(message) => boolean | Promise<boolean>`.\n// Truthy resolves the action through; falsy (or a rejected Promise) cancels it.\nexport function setConfirmResolver(fn) {\n confirmResolver = fn\n}\n"
6
6
  ],
7
- "mappings": "AA0BO,IAAI,EAAkB,CAAC,IAC5B,QAAQ,QAAQ,OAAO,OAAW,IAAc,OAAO,QAAQ,CAAO,EAAI,EAAI,EAIzE,SAAS,CAAkB,CAAC,EAAI,CACrC,EAAkB",
7
+ "mappings": "AAkCO,IAAI,EAAkB,CAAC,IAC5B,QAAQ,QAAQ,OAAO,OAAW,IAAc,OAAO,QAAQ,CAAO,EAAI,EAAI,EAIzE,SAAS,CAAkB,CAAC,EAAI,CACrC,EAAkB",
8
8
  "debugId": "95D50A0903B9B05E64756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1742,8 +1742,10 @@ export default class extends Controller {
1742
1742
  // so a dismissed/erroring dialog never surfaces as an unhandled rejection AND a
1743
1743
  // genuine bug inside #proceed is NOT silently swallowed. Enqueue ONLY on a
1744
1744
  // truthy resolution — nothing is enqueued, no timer scheduled, otherwise.
1745
+ // The resolver's optional 2nd arg (issue #222) carries the trigger element,
1746
+ // so an override has the same ctx shape here as on nestedRemove ({ el, … }).
1745
1747
  Promise.resolve()
1746
- .then(() => confirmResolver(message))
1748
+ .then(() => confirmResolver(message, { el: target }))
1747
1749
  .catch(() => false)
1748
1750
  .then((ok) => {
1749
1751
  if (ok) this.#proceed(target, action, params, debounce, throttle, optimistic, busy)
@@ -1758,6 +1760,9 @@ export default class extends Controller {
1758
1760
  // owns state that must survive re-renders).
1759
1761
  runOps(event) {
1760
1762
  const { ops, confirm, confirmWhen, outside, window: windowBound } = event.params
1763
+ // The trigger element on_client was spread onto (issue #222 ctx: { el }),
1764
+ // captured now — currentTarget resets before the confirm resolver's microtask.
1765
+ const trigger = event.currentTarget ?? event.target
1761
1766
 
1762
1767
  // Outside guard FIRST — identical semantics to dispatch() (issue #80): an
1763
1768
  // outside: trigger is a COMPLETE no-op for events inside this root, before
@@ -1789,7 +1794,7 @@ export default class extends Controller {
1789
1794
  // here (the user gesture), NOT in #applyOps: that applier is shared with the
1790
1795
  // server-pushed reactive:js stream action, which must NEVER prompt.
1791
1796
  Promise.resolve()
1792
- .then(() => confirmResolver(message))
1797
+ .then(() => confirmResolver(message, { el: trigger }))
1793
1798
  .catch(() => false)
1794
1799
  .then((ok) => {
1795
1800
  if (ok) this.#applyOps(this.#parseOps(ops))
@@ -2254,21 +2259,47 @@ export default class extends Controller {
2254
2259
  // else null. No confirm attr → null → the immediate-remove fast path.
2255
2260
  const confirm = trigger?.getAttribute?.("data-reactive-confirm-param")
2256
2261
  const confirmWhen = trigger?.getAttribute?.("data-reactive-confirm-when-param")
2257
- const message = this.#effectiveConfirmMessage(confirm, confirmWhen)
2258
- if (!message) return this.#removeNestedRow(row)
2262
+ const rawMessage = this.#effectiveConfirmMessage(confirm, confirmWhen)
2263
+ if (!rawMessage) return this.#removeNestedRow(row)
2264
+
2265
+ // Per-row confirm interpolation (issue #222). A row added client-side is a
2266
+ // cloneNode of the <template>, and the clone carries the TEMPLATE's confirm
2267
+ // string verbatim — the renumber/seed steps never rewrite the confirm attr.
2268
+ // So resolve %{field} placeholders here, from THIS row's live field values
2269
+ // (read now, not at clone time, so a later edit is reflected). An unresolved
2270
+ // key is left as its literal %{key} (debuggable, never throws). Server-
2271
+ // rendered rows already interpolate server-side, so their finished strings
2272
+ // carry no %{}; this is a no-op for them.
2273
+ const fields = this.#nestedRowObject(row)
2274
+ const message = this.#interpolateConfirm(rawMessage, fields)
2259
2275
 
2260
2276
  // Gate through the overridable confirmResolver (issues #52/#55/#178) — a
2261
- // themed dialog set with setConfirmResolver covers this trigger too. Call the
2262
- // resolver INSIDE the chain so even a SYNCHRONOUS override throw is a cancel
2263
- // (like a dismissed dialog), and remove ONLY on a truthy resolution.
2277
+ // themed dialog set with setConfirmResolver covers this trigger too. Pass the
2278
+ // row context (issue #222, superset of proposal 3) as an optional 2nd arg so
2279
+ // a power-user override can build the string itself; the message is already
2280
+ // interpolated for the default window.confirm path. Call the resolver INSIDE
2281
+ // the chain so even a SYNCHRONOUS override throw is a cancel (like a dismissed
2282
+ // dialog), and remove ONLY on a truthy resolution.
2264
2283
  return Promise.resolve()
2265
- .then(() => confirmResolver(message))
2284
+ .then(() => confirmResolver(message, { el: trigger, row, fields }))
2266
2285
  .catch(() => false)
2267
2286
  .then((ok) => {
2268
2287
  if (ok) this.#removeNestedRow(row)
2269
2288
  })
2270
2289
  }
2271
2290
 
2291
+ // Resolve %{field} placeholders in a confirm message from a row's field map
2292
+ // (issue #222). Ruby-style %{name} tokens; an unresolved key is left verbatim
2293
+ // (a visible, debuggable placeholder — never an empty hole or a throw). A
2294
+ // message with no placeholders returns unchanged, so this is inert for every
2295
+ // server-rendered (already-interpolated) confirm string.
2296
+ #interpolateConfirm(message, fields) {
2297
+ if (!message.includes("%{")) return message
2298
+ return message.replace(/%\{(\w+)\}/g, (whole, key) =>
2299
+ Object.prototype.hasOwnProperty.call(fields, key) ? fields[key] : whole,
2300
+ )
2301
+ }
2302
+
2272
2303
  // The remove itself, shared by the confirmed and no-confirm paths. Draft rows
2273
2304
  // leave the DOM; a persisted row (a hidden [_destroy] input present) is marked
2274
2305
  // "1" + hidden instead (set-value + dispatch contract, #183), so Rails destroys
@@ -1,4 +1,4 @@
1
- import{Controller as WX}from"@hotwired/stimulus";import{confirmResolver as R}from"phlex/reactive/confirm";import{computeReducer as JX}from"phlex/reactive/compute";import{confirmPredicate as LX}from"phlex/reactive/confirm_predicate";function VX(){let X=window.Turbo?.StreamActions;if(!X||X["reactive:visit"])return;X["reactive:visit"]=function(){let Z=this.getAttribute("data-url");if(Z)window.Turbo.visit(Z,{action:"advance"})}}function BX(){let X=window.Turbo?.StreamActions;if(!X||X["reactive:token"])return;X["reactive:token"]=function(){let Z=this.getAttribute("data-reactive-token-value"),$=this.getAttribute("target");if(!Z||!$)return;let Q=document.getElementById($);if(Q)Q.setAttribute("data-reactive-token-value",Z)}}function MX(){let X=window.Turbo?.StreamActions;if(!X||X["reactive:js"])return;X["reactive:js"]=function(){let Z=HX(this.getAttribute("data-reactive-ops"));if(!Z.length)return;let $=this.getAttribute("target"),Q=$?document.getElementById($):null;if($&&!Q)return;UX(Z,(j)=>ZZ(j,Q))}}var M=new Map;function s(X,Z){let $=!M.has(X);if(M.set(X,Z),$)QX()}function N(X){if(M.delete(X))jX()}function GZ(){M.clear(),S=!1}function qZ(X){return M.get(X)?.via}var S=!1;function _X(){let X=window.Turbo?.StreamActions;if(!X||X["reactive:defer"])return;if(X["reactive:defer"]=function(){let Z=this.getAttribute("target");if(!Z)return;if(this.getAttribute("data-reactive-defer-via")==="stream"){OX(Z,this);return}let $=this.getAttribute("data-reactive-defer-token");if(!$)return;C(Z,$)},!S&&typeof document<"u"&&document.addEventListener)S=!0,document.addEventListener("turbo:before-stream-render",AX)}function AX(X){let $=X.target?.getAttribute?.("target");if(!$)return;let Q=$.startsWith("reactive-defer-src-")?$.slice(19):$;if(M.get(Q)?.via==="stream")N(Q)}function C(X,Z){let $=document.getElementById(X);if(!$){console.warn(`[phlex-reactive] reactive:defer target #${X} is not on the page — skipped`);return}a(X),r($);let Q={via:"fetch",abort:new AbortController,timedOut:!1};s(X,Q),xX(X,Q,Z)}function OX(X,Z){let $=document.getElementById(X);if(!$){console.warn(`[phlex-reactive] reactive:defer target #${X} is not on the page — skipped`);return}let Q=Z.getAttribute("data-reactive-defer-src");if(!Q)return;if(!globalThis.customElements?.get?.("pgbus-stream-source")){let z=Z.getAttribute("data-reactive-defer-token");if(z){C(X,z);return}console.error("[phlex-reactive] reactive:defer via=stream but <pgbus-stream-source> is not registered "+"and no fallback token was provided — is the pgbus client loaded on this page?");return}a(X),r($);let j=document.createElement("pgbus-stream-source");j.id=n(X),j.setAttribute("src",Q),j.setAttribute("since-id",Z.getAttribute("data-reactive-defer-since-id")??"0"),j.setAttribute("hidden",""),document.body.appendChild(j),s(X,{via:"stream"})}function n(X){return`reactive-defer-src-${X}`}async function xX(X,Z,$){let Q=setTimeout(()=>{Z.timedOut=!0,Z.abort.abort()},CX()),j;try{j=await fetch(PX(),{method:"POST",headers:{Accept:"text/vnd.turbo-stream.html","Content-Type":"application/json","X-CSRF-Token":NX()},body:JSON.stringify({token:$}),credentials:"same-origin",signal:Z.abort.signal})}catch(G){if(clearTimeout(Q),M.get(X)!==Z)return;console.error("[phlex-reactive] deferred render failed",G),F(X,$);return}if(M.get(X)!==Z){clearTimeout(Q);return}if(j.status===204){clearTimeout(Q),g(X);return}if(!j.ok){clearTimeout(Q),console.error(`[phlex-reactive] deferred render failed: HTTP ${j.status}`),F(X,$,j.status);return}let z;try{z=await j.text()}catch(G){if(clearTimeout(Q),M.get(X)!==Z)return;console.error("[phlex-reactive] deferred render failed reading the body",G),F(X,$);return}if(clearTimeout(Q),M.get(X)!==Z)return;g(X),window.Turbo.renderStreamMessage(z)}function a(X){let Z=M.get(X);if(!Z)return;if(N(X),Z.via==="fetch")Z.abort.abort();else document.getElementById(n(X))?.remove?.()}function r(X){X.setAttribute("data-reactive-defer-pending","true"),X.setAttribute("aria-busy","true")}function t(X){X.removeAttribute("data-reactive-defer-pending"),X.removeAttribute("aria-busy")}function g(X){N(X);let Z=document.getElementById(X);if(!Z)return;t(Z),Z.removeAttribute("data-reactive-error")}function F(X,Z,$){N(X);let Q=document.getElementById(X);if(!Q)return;t(Q),Q.setAttribute("data-reactive-error","defer");let j=()=>{let z=document.getElementById(X);if(!z){console.warn("[phlex-reactive] defer retry() ignored — the target left the DOM");return}z.removeAttribute("data-reactive-error"),C(X,Z)};Q.dispatchEvent(new CustomEvent("reactive:error",{bubbles:!0,composed:!0,detail:{kind:"defer",target:X,status:$,retry:j}}))}function PX(){return document.querySelector('meta[name="phlex-reactive-defer-path"]')?.content||"/reactive/defer"}function NX(){return document.querySelector('meta[name="csrf-token"]')?.content??""}function CX(){let X=document.querySelector('meta[name="phlex-reactive-timeout"]')?.content,Z=Number(X);return Number.isFinite(Z)&&Z>0?Z:30000}var b=!1;function DX(){if(b)return;if(typeof document>"u"||!document.addEventListener)return;b=!0,document.addEventListener("turbo:before-stream-render",RX)}function RX(X){let Z=X.detail,$=Z?.render;if(typeof $!=="function"||$.__reactiveDismissWrapped){if(typeof requestAnimationFrame==="function")requestAnimationFrame(I);else setTimeout(I,0);return}let Q=async(j)=>{await $(j),I()};Q.__reactiveDismissWrapped=!0,Z.render=Q}function I(){let X=document.querySelectorAll("[data-reactive-dismiss-after]");for(let Z of X){if(Z.hasAttribute("data-reactive-dismiss-scheduled"))continue;let $=Number(Z.getAttribute("data-reactive-dismiss-after"));if(!Number.isFinite($)||$<=0)continue;Z.setAttribute("data-reactive-dismiss-scheduled",""),setTimeout(()=>Z.remove(),$)}}function YZ(){b=!1}var FX=Object.freeze({append:"enter",prepend:"enter",replace:"update",update:"update",remove:"exit"}),T=Object.freeze(["fade","slide","scale","highlight","shake"]),w="data-reactive-fx-pending",e=1000,h=!1;function IX(){if(h)return;if(typeof document>"u"||typeof document.addEventListener!=="function")return;h=!0,document.addEventListener("turbo:before-stream-render",TX)}function KZ(){h=!1}function TX(X){let Z=X.detail,$=Z?.render;if(typeof $!=="function"||$.__reactiveEffectsWrapped)return;let Q=Z?.newStream??X.target,j=FX[Q?.getAttribute?.("action")];if(!j||SX())return;let z=EX(Q,j);if(!z)return;let G=j==="exit"?async(q)=>{await hX(x(Q),z),await $(q)}:async(q)=>{let Y=j==="enter"?bX(Q):null;if(await $(q),j==="enter")wX(Y,z);else XX(x(Q),z)};G.__reactiveEffectsWrapped=!0,Z.render=G}function EX(X,Z){let $=X.getAttribute?.("data-reactive-effect");if($==="off")return null;if($)return c($,Z);let j=(Z==="enter"?kX(X):x(X))?.getAttribute?.(`data-reactive-effect-${Z}`);return j?c(j,Z):null}function x(X){let Z=X.getAttribute?.("target");return Z?document.getElementById?.(Z)??null:null}function kX(X){return X.querySelector?.("template")?.content?.firstElementChild??null}function c(X,Z){if(X.startsWith("[")){let Q=null;try{let j=JSON.parse(X);if(Array.isArray(j)&&j.length===3)Q=j.map(String)}catch{}if(Q)return{legs:Q};return console.warn(`[phlex-reactive] malformed effect legs ${JSON.stringify(X)} — skipped`),null}let $=X==="random"?T[Math.floor(Math.random()*T.length)]:X;if(!T.includes($))return console.warn(`[phlex-reactive] unknown effect ${JSON.stringify(X)} — skipped`),null;return{className:`reactive-fx--${$}-${Z}`}}function SX(){try{return typeof matchMedia==="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches}catch{return!1}}function bX(X){let Z=X.querySelector?.("template")?.content;if(!Z)return null;for(let $ of Array.from(Z.children??[]))$.setAttribute?.(w,"");return x(X)}function wX(X,Z){if(typeof X?.querySelectorAll!=="function")return;for(let $ of Array.from(X.querySelectorAll(`[${w}]`)))$.removeAttribute(w),XX($,Z)}async function hX(X,Z){if(!X?.classList)return;if(Z.legs){await ZX(X,Z.legs);return}X.classList.add(Z.className);let $=f(X);if($<=0){X.classList.remove(Z.className);return}await p(X,$),X.classList.remove(Z.className)}function XX(X,Z){if(!X?.classList)return;if(Z.legs){ZX(X,Z.legs);return}if(X.classList.contains(Z.className))X.classList.remove(Z.className),X.offsetWidth;X.classList.add(Z.className);let $=f(X);if($<=0){X.classList.remove(Z.className);return}let Q=X.__reactiveFxToken=(X.__reactiveFxToken??0)+1;p(X,$).then(()=>{if(X.__reactiveFxToken===Q)X.classList.remove(Z.className)})}async function ZX(X,Z){let[$,Q,j]=Z.map(yX),z=X.__reactiveFxToken=(X.__reactiveFxToken??0)+1;if(X.classList.remove(...$,...Q,...j),X.classList.add(...$,...Q),await vX(),X.__reactiveFxToken!==z)return;X.classList.remove(...Q),X.classList.add(...j);let G=f(X);if(G>0)await p(X,G);if(X.__reactiveFxToken!==z)return;X.classList.remove(...$,...j)}function yX(X){return String(X??"").split(/\s+/).filter(Boolean)}function f(X){if(typeof getComputedStyle!=="function")return 0;try{let Z=getComputedStyle(X),$=(z)=>String(z??"").split(",").reduce((G,q)=>Math.max(G,parseFloat(q)||0),0),Q=$(Z.animationDuration)+$(Z.animationDelay),j=$(Z.transitionDuration)+$(Z.transitionDelay);return Math.min(Math.max(Q,j)*1000,e)}catch{return 0}}function p(X,Z){return new Promise(($)=>{let Q=!1,j=()=>{if(Q)return;Q=!0,$()};X.addEventListener?.("animationend",j,{once:!0}),X.addEventListener?.("transitionend",j,{once:!0}),setTimeout(j,Math.min(Z+50,e))})}function vX(){return new Promise((X)=>{if(typeof requestAnimationFrame==="function")requestAnimationFrame(()=>X());else setTimeout(X,16)})}var y=!1;function fX(){if(y)return;if(typeof window>"u"||typeof document>"u")return;if(typeof window.addEventListener!=="function")return;y=!0;let X=()=>{let Z=document.documentElement;if(typeof Z?.toggleAttribute!=="function")return;Z.toggleAttribute("data-reactive-offline",globalThis.navigator?.onLine===!1)};X(),window.addEventListener("online",X),window.addEventListener("offline",X)}function HZ(){y=!1}var m="phlex-reactive:latency",P=!1;function pX(X){if(typeof sessionStorage>"u")return;sessionStorage.setItem(m,String(X))}function mX(){if(typeof sessionStorage>"u")return;sessionStorage.removeItem(m),P=!1}function uX(){if(typeof window>"u"||typeof document>"u")return;if(document.querySelector?.('meta[name="phlex-reactive-env"]')?.content!=="development")return;window.PhlexReactive={enableLatencySim:pX,disableLatencySim:mX}}function UZ(){P=!1}var $X="data-reactive-active",_=0;function QX(){if(_++,_===1)zX("reactive:busy")}function jX(){if(_===0)return;if(_--,_===0)zX("reactive:idle")}function WZ(){return _}function JZ(){_=0,(typeof document<"u"?document.documentElement:null)?.removeAttribute?.($X)}function zX(X){if(typeof document>"u")return;let Z=document.documentElement;if(typeof Z?.toggleAttribute==="function")Z.toggleAttribute($X,_>0);if(typeof document.dispatchEvent==="function"&&typeof CustomEvent==="function")document.dispatchEvent(new CustomEvent(X,{detail:{count:_}}))}function d(){VX(),BX(),MX(),_X(),DX(),IX(),fX(),uX()}function gX(X){return X.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}if(typeof window<"u")if(window.Turbo)d();else document.addEventListener("turbo:load",d,{once:!0});var D=!1;function cX(){if(D)return;if(typeof document>"u")return;let X=document.querySelectorAll('[data-controller~="reactive"]');if(!X||X.length===0)return;console.warn("[phlex-reactive] found "+X.length+' element(s) with data-controller="reactive" '+"but the reactive controller never connected. It is loaded but not registered — "+'add `application.register("reactive", ReactiveController)` (importmap) or import it into app/javascript/controllers/ for lazyLoadControllersFrom apps. See the README.')}function LZ(){D=!1}function VZ(){D=!0}if(typeof window<"u"&&typeof document<"u"){let X=()=>setTimeout(cX,0);if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",X,{once:!0});else X()}var dX=new Set(["href","src","srcdoc","action","formaction","xlink:href","style"]);function lX(X){let Z=String(X).toLowerCase();return Z.startsWith("on")||dX.has(Z)}function iX(X,Z,$){let[Q,j,z]=Z;X.classList.add(Q,j),$(),requestAnimationFrame(()=>{X.classList.remove(j),X.classList.add(z)});let G=!1,q=()=>{if(G)return;G=!0,X.classList.remove(Q,z)};X.addEventListener("animationend",q,{once:!0}),setTimeout(q,350)}var l=Object.freeze({show:(X,Z)=>E(X,!1,Z),hide:(X,Z)=>E(X,!0,Z),toggle:(X,Z)=>E(X,!X.hidden,Z),add_class:(X,Z)=>X.classList.add(...Z.classes??[]),remove_class:(X,Z)=>X.classList.remove(...Z.classes??[]),toggle_class:(X,Z)=>(Z.classes??[]).forEach(($)=>X.classList.toggle($)),set_attr:(X,Z)=>{if(k(Z.name))X.setAttribute(Z.name,Z.value??"")},remove_attr:(X,Z)=>{if(k(Z.name))X.removeAttribute(Z.name)},toggle_attr:(X,Z)=>{if(!k(Z.name))return;if(X.hasAttribute(Z.name))X.removeAttribute(Z.name);else X.setAttribute(Z.name,"")},focus:(X)=>X.focus?.(),focus_first:(X)=>XZ(X)?.focus?.(),text:(X,Z)=>{let $=String(Z.value??"");if(X.textContent!==$)X.textContent=$},dispatch:(X,Z)=>{X.dispatchEvent(new CustomEvent(Z.name,{bubbles:!0,composed:!0,detail:Z.detail??{}}))}});function E(X,Z,$){if($?.transition)iX(X,$.transition,()=>X.hidden=Z);else X.hidden=Z}function k(X){if(!lX(X))return!0;return console.warn(`[phlex-reactive] refused client attr op on ${JSON.stringify(X)} — skipped`),!1}var GX=/^#[A-Za-z_][\w-]*$/;function oX(X){if(typeof X==="string"&&GX.test(X))return!0;return console.warn(`[phlex-reactive] refused cross-root mirror target ${JSON.stringify(X)} — skipped`),!1}function sX(X,Z){let $=X.getAttribute("data-reactive-show-equals");if($!==null)return Z===$;let Q=X.getAttribute("data-reactive-show-not");if(Q!==null)return Z!==Q;let j=X.getAttribute("data-reactive-show-in");if(j!==null){try{let z=JSON.parse(j);if(Array.isArray(z))return z.includes(Z)}catch{}return console.warn(`[phlex-reactive] malformed reactive_show in: list ${JSON.stringify(j)} — skipped`),null}for(let z of qX){let G=X.getAttribute(`data-reactive-show-${z}`);if(G!==null)return YX(z,G,Z)}return console.warn("[phlex-reactive] a reactive_show binding declares no predicate — skipped"),null}var qX=["gte","gt","lte","lt"];function YX(X,Z,$){let Q=Number(Z);if(Number.isNaN(Q))return console.warn(`[phlex-reactive] reactive_show ${X}: needs a numeric literal, got ${JSON.stringify(Z)} — skipped`),null;let j=$==null?"":String($).trim(),z=j===""?NaN:Number(j);if(Number.isNaN(z))return!1;switch(X){case"gte":return z>=Q;case"gt":return z>Q;case"lte":return z<=Q;case"lt":return z<Q;default:return null}}function KX(X,Z){if(!X||typeof X!=="object")return null;if(typeof X.equals==="string")return Z===X.equals;if(typeof X.not==="string")return Z!==X.not;if(Array.isArray(X.in))return X.in.includes(Z);for(let $ of qX)if($ in X)return YX($,X[$],Z);return null}var i="[data-reactive-show-field], [data-reactive-show]";function nX(X){try{let Z=JSON.parse(X);if(Z&&typeof Z==="object"&&!Array.isArray(Z))return Z}catch{}return console.warn(`[phlex-reactive] malformed compound reactive_show payload ${JSON.stringify(X)} — skipped`),null}function u(X,Z){if(!X||typeof X!=="object"||typeof X.field!=="string")return!1;let $=Z(X.field)??"";return KX(X,$)===!0}function v(X,Z){if(!Array.isArray(X)||X.length===0)return null;return X.some(($)=>Array.isArray($)&&$.length>0&&$.every((Q)=>u(Q,Z)))}function aX(X){if(!Array.isArray(X)||X.length===0)return null;let Z=new Set;for(let $ of X){if(!Array.isArray($))continue;for(let Q of $)if(Q&&typeof Q==="object"&&typeof Q.field==="string")Z.add(Q.field)}return Z.size>0?[...Z]:null}function rX(X,Z){if(!X||typeof X!=="object")return null;let $=X.any;if(Array.isArray($)&&($.length===0||Array.isArray($[0])))return v($,Z);return tX(X,Z)}function tX(X,Z){let $=Array.isArray(X.all)?"all":Array.isArray(X.any)?"any":null;if(!$)return null;let Q=X[$];if(Q.length===0)return null;let j=Q.map((z)=>u(z,Z));return $==="all"?j.every(Boolean):j.some(Boolean)}function o(X){if(typeof X==="string"&&GX.test(X))return!0;return console.warn(`[phlex-reactive] refused cross-root show target ${JSON.stringify(X)} — skipped`),!1}var eX='a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';function XZ(X){return X.querySelectorAll?.(eX)?.[0]??null}function HX(X){if(Array.isArray(X))return X;if(typeof X!=="string")return[];try{let Z=JSON.parse(X);return Array.isArray(Z)?Z:[]}catch{return[]}}function UX(X,Z){for(let $ of X){if(!Array.isArray($))continue;let[Q,j={}]=$;if(!Object.hasOwn(l,Q)){console.warn(`[phlex-reactive] unknown client op ${JSON.stringify(Q)} — skipped`);continue}for(let z of Z(j))l[Q](z,j)}}function ZZ(X,Z){let $=X.to;if(Z){if($==="@root")return[Z];if(typeof $!=="string"||$==="")return[];if(X.global)return[...document.querySelectorAll($)];return[...Z.querySelectorAll($)]}if(typeof $!=="string"||$===""||$==="@root")return[];return[...document.querySelectorAll($)]}class BZ extends WX{static values={token:String};#m;#A=new Map;#H=new Map;#BX;#I;#u;#T=0;#U=new Map;#E=new WeakMap;#O=new Map;#g=new WeakSet;#W;#J;#L;#Z;#Q;#V;#c=!1;#k=0;#d=!1;#j;#B;#M;#x;connect(){if(D=!0,this.element.id==="")console.warn("[phlex-reactive] a reactive root has no id; its next-action token can't self-match "+"and may fall back to the first token in the response → a silent HTTP 403 on the NEXT action. "+"Put id: on the SAME element as reactive_attrs — use div(**reactive_root) (emits id + token together), "+"or div(id:, **reactive_attrs). The id: must NOT be on a child. See the README.");if(this.element.getAttribute?.("data-reactive-defer-token"))this.#l(),this.#x=()=>this.#l(),this.element.addEventListener?.("turbo:morph-element",this.#x);if(this.#MX()){if(this.#W=()=>this.#h(),this.element.addEventListener?.("turbo:morph-element",this.#W),this.#h(),this.element.getAttribute?.("data-reactive-warn-unsaved")==="true")this.#gX()}if(this.#dX())this.#Z=()=>this.#zX(),this.element.addEventListener?.("input",this.#Z),this.element.addEventListener?.("change",this.#Z),this.element.addEventListener?.("turbo:morph-element",this.#Z),this.#zX();if(this.#R())this.#Q=(X)=>{if(X?.type==="input"&&!this.#rX(X))return;this.#_()},this.element.addEventListener?.("input",this.#Q),this.element.addEventListener?.("turbo:morph-element",this.#Q),this.#_();if(this.#F())this.#V=()=>this.#f(),this.element.addEventListener?.("turbo:morph-element",this.#V),this.#f();if(this.#eX())this.#j=(X)=>this.syncNestedJson(X),this.#B=()=>this.#N(),this.element.addEventListener?.("input",this.#j),this.element.addEventListener?.("change",this.#j),this.element.addEventListener?.("turbo:morph-element",this.#B),this.#N();if(this.#aX())this.#M=()=>this.recompute(),this.element.addEventListener?.("turbo:morph-element",this.#M),this.recompute()}#MX(){if((this.element.getAttribute?.("data-action")??"").includes("reactive#trackDirty"))return!0;let X=this.element.querySelectorAll?.('[data-action*="reactive#trackDirty"]')??[];for(let Z of X)if(this.#$(Z))return!0;return!1}disconnect(){if(this.#kX(),this.#bX(),this.#cX(),this.#nX(),this.#tX(),this.#GZ(),this.#qZ(),this.#YZ(),this.#x)this.element.removeEventListener?.("turbo:morph-element",this.#x)}#l(){let X=this.element;if(!X?.id)return;let Z=X.getAttribute?.("data-reactive-defer-token");if(!Z)return;if(X.getAttribute?.("data-reactive-defer-pending")!=="true")return;C(X.id,Z)}dispatch(X){let{action:Z,params:$,debounce:Q,throttle:j,confirm:z,confirmWhen:G,outside:q,window:Y,optimistic:K}=X.params;if(!Z)return;let W=X.params.busy??this.#_Z(X.params.loading);if(q&&this.element.contains(X.target))return;let B=X.currentTarget??X.target;if(!Y&&!this.#UZ(K,B))X.preventDefault();let J=this.#w(z,G);if(!J)return this.#t(B,Z,$,Q,j,K,W);Promise.resolve().then(()=>R(J)).catch(()=>!1).then((L)=>{if(L)this.#t(B,Z,$,Q,j,K,W)})}runOps(X){let{ops:Z,confirm:$,confirmWhen:Q,outside:j,window:z}=X.params;if(j&&this.element.contains(X.target))return;if(!z)X.preventDefault();let G=this.#w($,Q);if(!G)return this.#UX(this.#HX(Z));Promise.resolve().then(()=>R(G)).catch(()=>!1).then((q)=>{if(q)this.#UX(this.#HX(Z))})}trackDirty(){this.#h()}recompute(X){if(X&&this.#g.has(X))return;let Z=this.#CX(),$=Z.map(([H])=>H),Q=this.element.getAttribute?.("data-reactive-scope")||null,j=(H)=>Q&&!H.includes("[")?`${Q}[${H}]`:H,z=this.#X(),G=new Map,q=(H)=>{if(G.has(H))return G.get(H);let U=null;for(let A of this.element.querySelectorAll(`[name="${j(H)}"]`))if(z(A)){U=A;break}return G.set(H,U),U};for(let H of $)this.#a(H,q(H)?.value??"");let Y=this.element.getAttribute("data-reactive-compute-reducer-param"),K=Y?JX(Y):null;if(!K){this.#r({},q);return}let W=this.#NX("data-reactive-compute-outputs-param"),B={};for(let[H,U]of Z){let A=q(H);if(U==="string")B[H]=A?.value??"";else{let V=Number(A?.value);B[H]=Number.isFinite(V)?V:0}}let J=K(B,{changed:this.#DX(X,$,Q)})||{},L=[];for(let H of W){if(!(H in J))continue;let U=q(H);if(!U)continue;if(String(J[H])===U.value)continue;U.value=J[H],L.push(U)}for(let H of Object.keys(J)){let U=J[H];if(U===void 0||U===null)continue;this.#a(H,U)}this.#r(J,q);for(let H of L){let U=new Event("input",{bubbles:!0});this.#g.add(U),H.dispatchEvent(U)}}listnavNext(X){this.#i(X,1)}listnavPrev(X){this.#i(X,-1)}listnavPick(X){let Z=this.#P(X),$=Z.findIndex((Q)=>Q.hasAttribute("data-reactive-highlighted"));if($<0)return;X.preventDefault(),Z[$].click()}listnavClose(X){for(let Z of this.#P(X))Z.removeAttribute("data-reactive-highlighted")}#i(X,Z){let $=this.#P(X);if(!$.length)return;X.preventDefault();let Q=$.findIndex((G)=>G.hasAttribute("data-reactive-highlighted")),j=Q<0?Z>0?0:$.length-1:(Q+Z+$.length)%$.length;for(let G of $)G.removeAttribute("data-reactive-highlighted");let z=$[j];z.setAttribute("data-reactive-highlighted","true"),z.scrollIntoView?.({block:"nearest"})}#P(X){let $=(X?.currentTarget??X?.target??this.element).getAttribute?.("data-reactive-listnav-option-param")??this.element.getAttribute("data-reactive-listnav-option-param");if(!$)return[];let Q=this.#X();return Array.from(this.element.querySelectorAll($)).filter((j)=>!j.hidden&&Q(j))}tagsAdd(X){if(!this.#F())return;if(X?.defaultPrevented)return;if(this.#P(X).some((Q)=>Q.hasAttribute?.("data-reactive-highlighted")))return;X?.preventDefault?.();let Z=X?.currentTarget??X?.target;if(!Z)return;if(!this.#qX(String(Z.value??"").split(",")))return;if(Z.value="",this.#R())this.#_()}tagsPick(X){if(!this.#F())return;X?.preventDefault?.();let $=(X?.currentTarget??X?.target)?.getAttribute?.("data-reactive-tag-param");if(!$)return;if(!this.#qX([$]))return;let Q=this.#XZ();if(!Q)return;Q.value="",this.#_(),Q.focus?.()}tagsRemove(X){if(!this.#F())return;X?.preventDefault?.();let $=(X?.currentTarget??X?.target)?.getAttribute?.("data-reactive-tag-param");if(!$)return;let Q=this.#y();if(!Q)return;let j=this.#v(Q),z=j.filter((G)=>G.toLowerCase()!==$.toLowerCase());if(z.length===j.length)return;this.#YX(Q,z)}nestedAdd(X){X?.preventDefault?.();let Z=X?.currentTarget??X?.target,$=Z?.getAttribute?.("data-reactive-association-param");if(!$)return;if(typeof this.element?.querySelectorAll!=="function")return;let Q=this.#X(),j=[...this.element.querySelectorAll(`[data-reactive-nested-list="${$}"]`)].find(Q),G=[...this.element.querySelectorAll(`[data-reactive-nested-template="${$}"]`)].find(Q)?.content?.firstElementChild;if(!j||!G){this.#zZ($);return}let q=G.cloneNode(!0);this.#jZ(q,this.#QZ()),j.appendChild(q);let Y=Z?.getAttribute?.("data-reactive-nested-from-param"),K=Z?.getAttribute?.("data-reactive-nested-clear-param")==="true",W=this.#_X(q,Y,K);if(Y)W?.focus?.();else[...q.querySelectorAll?.("input, select, textarea")??[]][0]?.focus?.();if(j.getAttribute?.("data-reactive-nested-json")===$)this.#s($)}#_X(X,Z,$){if(!Z)return null;let Q;try{Q=JSON.parse(Z)}catch{return null}if(!Q||typeof Q!=="object")return null;let j=this.#X(),z=[...X.querySelectorAll?.("input, select, textarea")??[]],G=[];for(let[q,Y]of Object.entries(Q)){let K=[...this.element.querySelectorAll?.(Y)??[]].find(j);if(!K)continue;let W=z.find((B)=>this.#n(B.getAttribute?.("name"))===q);if(!W)continue;this.#AX(W,K),G.push(K)}if($)for(let q of G)this.#OX(q);return G[0]??null}#AX(X,Z){if(X.type==="checkbox")X.checked=Z.type==="checkbox"?!!Z.checked:this.#S(Z)!=="";else X.value=this.#S(Z);if(typeof X.dispatchEvent==="function")X.dispatchEvent(new Event("input",{bubbles:!0}))}#OX(X){if(X.type==="checkbox")X.checked=!1;else X.value="";if(typeof X.dispatchEvent==="function")X.dispatchEvent(new Event("input",{bubbles:!0}))}nestedRemove(X){X?.preventDefault?.();let Z=X?.currentTarget??X?.target,$=Z?.closest?.("[data-reactive-nested-row]");if(!$)return;if($.closest?.('[data-controller~="reactive"]')!==this.element)return;let Q=Z?.getAttribute?.("data-reactive-confirm-param"),j=Z?.getAttribute?.("data-reactive-confirm-when-param"),z=this.#w(Q,j);if(!z)return this.#o($);return Promise.resolve().then(()=>R(z)).catch(()=>!1).then((G)=>{if(G)this.#o($)})}#o(X){let Z=[...X.querySelectorAll?.('input[name$="[_destroy]"]')??[]][0];if(Z){if(Z.value="1",typeof Z.dispatchEvent==="function")Z.dispatchEvent(new Event("input",{bubbles:!0}));X.hidden=!0}else X.parentNode?.removeChild?.(X);this.#N()}syncNestedJson(X){let Z=X?.target;if(!Z||!this.#$(Z))return;this.#N()}#N(){if(typeof this.element?.querySelectorAll!=="function")return;let X=this.#X();for(let Z of[...this.element.querySelectorAll("[data-reactive-nested-json]")].filter(X))this.#s(Z.getAttribute("data-reactive-nested-json"))}#s(X){let Z=this.#X(),$=[...this.element.querySelectorAll(`[data-reactive-nested-list="${X}"]`)].find(Z);if(!$)return;let Q=this.#xX($);if(!Q)return;let j=[];for(let G of[...$.querySelectorAll?.("[data-reactive-nested-row]")??[]]){if(!Z(G)||G.hidden)continue;j.push(this.#PX(G))}let z=JSON.stringify(j);if(Q.value===z)return;if(Q.value=z,typeof Q.dispatchEvent==="function")Q.dispatchEvent(new Event("input",{bubbles:!0}))}#xX(X){let Z=X.getAttribute?.("data-reactive-nested-json-field");if(!Z)return null;let $=this.#X();return[...this.element.querySelectorAll(Z)].find($)??null}#PX(X){let Z={};for(let $ of[...X.querySelectorAll?.("input, select, textarea")??[]]){let Q=this.#n($.getAttribute?.("name"));if(Q===null||Q==="_destroy")continue;Z[Q]=this.#S($)}return Z}#n(X){if(!X)return null;let Z=X.match(/\[([^\][]+)\]$/);return Z?Z[1]:X}#S(X){if(X.type==="checkbox")return X.checked?X.value||"on":"";return X.value??""}#NX(X){let Z=this.element.getAttribute(X);if(!Z)return[];try{let $=JSON.parse(Z);return Array.isArray($)?$:[]}catch{return[]}}#CX(){let X=this.element.getAttribute("data-reactive-compute-inputs-param");if(!X)return[];try{let Z=JSON.parse(X);if(Array.isArray(Z))return Z.map(($)=>[$,"number"]);if(Z&&typeof Z==="object")return Object.entries(Z);return[]}catch{return[]}}#DX(X,Z,$){let Q=X?.target;if(!Q?.name||typeof Q.closest!=="function")return null;let j=this.#RX(Q.name,$);if(!Z.includes(j))return null;return this.#$(Q)?j:null}#RX(X,Z){if(!Z)return X;let $=`${Z}[`;return X.startsWith($)&&X.endsWith("]")?X.slice($.length,-1):X}#a(X,Z){let $=String(Z);for(let Q of this.#FX(X)){if(Q.textContent===$)continue;Q.textContent=$}}#FX(X){let Z=this.element.querySelectorAll(`[data-reactive-text="${X}"]`);return Array.from(Z).filter(($)=>this.#$($))}#r(X,Z){let $=this.#IX();for(let[Q,j]of Object.entries($)){let z=Q in X?X[Q]:Z(Q)?.value;if(z===void 0||z===null)continue;let G=String(z);for(let q of Array.isArray(j)?j:[j]){if(!oX(q))continue;for(let Y of document.querySelectorAll(q)){if(Y.textContent===G)continue;Y.textContent=G}}}}#IX(){let X=this.element.getAttribute("data-reactive-compute-mirror-param");if(!X)return{};try{let Z=JSON.parse(X);return Z&&typeof Z==="object"&&!Array.isArray(Z)?Z:{}}catch{return{}}}#t(X,Z,$,Q,j,z,G){if(this.#D("reactive:before-dispatch",{action:Z,params:this.#KX($),element:this.element},{cancelable:!0}).defaultPrevented)return;let Y=Number(Q)||0;if(Y>0)return this.#EX(X,Y,Z,$,z,G);let K=Number(j)||0;if(K>0)return this.#SX(X,K,Z,$,z,G);return this.#C(Z,$,z,X,G)}#C(X,Z,$,Q,j){let z=this.#WZ($,Q),G=this.#JZ(X,Q,j),q=this.#e()?this.#TX($,Q):null;return this.queue=(this.queue??Promise.resolve()).then(()=>this.#pX(X,Z,z,G,q)),this.queue}#TX(X,Z){if(!X?.hide)return null;let $=this.#LX(X,Z);if(!$.length)return null;return()=>{let Q=$.filter((j)=>j.isConnected&&!j.hidden);if(!Q.length)return;console.warn("[phlex-reactive] optimistic: { hide: true } was undone by the reply's re-render — "+"the element is visible again. For an instant delete, return reply.remove so the server removes it; otherwise the hide only flashes.",Q)}}#EX(X,Z,$,Q,j,z){this.#b(X);let G=()=>{this.#b(X),this.#C($,Q,j,X,z)},q=setTimeout(G,Z);X?.addEventListener?.("blur",G,{once:!0}),this.#A.set(X,{timer:q,flush:G})}#b(X){let Z=this.#A.get(X);if(!Z)return;clearTimeout(Z.timer),X?.removeEventListener?.("blur",Z.flush),this.#A.delete(X)}#kX(){for(let X of[...this.#A.keys()])this.#b(X)}#SX(X,Z,$,Q,j,z){let G=this.#H.get(X)??new Map;if(G.has($))return;let q=setTimeout(()=>{if(G.delete($),G.size===0)this.#H.delete(X)},Z);return G.set($,q),this.#H.set(X,G),this.#C($,Q,j,X,z)}#bX(){for(let X of this.#H.values())for(let Z of X.values())clearTimeout(Z);this.#H.clear()}#D(X,Z,{cancelable:$=!1}={}){let Q=new CustomEvent(X,{bubbles:!0,composed:!0,cancelable:$,detail:Z});return(this.element.isConnected?this.element:document).dispatchEvent(Q),Q}#z(X,Z,$,Q){let j=()=>{if(!this.element.isConnected){console.warn("[phlex-reactive] retry() ignored — the reactive root left the DOM");return}return this.#C(X,Z)};this.#D("reactive:error",{action:X,params:$,...Q,retry:j})}#G(X){if(this.element?.isConnected===!1)return;this.element?.setAttribute?.("data-reactive-error",X)}#wX(){this.element?.removeAttribute?.("data-reactive-error")}#hX(){let X=document.querySelector("[data-reactive-error-flash]");if(!X?.content)return;let Z=X.getAttribute("data-reactive-error-flash")||"flash",$=document.getElementById(Z);if(!$)return;$.appendChild(X.content.cloneNode(!0))}#yX(){if(typeof sessionStorage>"u")return Promise.resolve();let X=Number(sessionStorage.getItem(m));if(!Number.isFinite(X)||X<=0)return Promise.resolve();if(!P)P=!0,console.warn(`[phlex-reactive] latency simulator ACTIVE — every action is delayed by ${X}ms. Call PhlexReactive.disableLatencySim() (or clear sessionStorage) to turn it off.`);return new Promise((Z)=>setTimeout(Z,X))}#e(){return this.element?.getAttribute?.("data-reactive-debug")==="true"}#XX(){return typeof performance<"u"&&typeof performance.now==="function"?performance.now():Date.now()}#vX(X){if(!X)return[];let Z=[],$=/<turbo-stream\b([^>]*)>/g,Q;while((Q=$.exec(X))!==null){let j=Q[1],z=j.match(/\baction="([^"]*)"/)?.[1]??"?",G=j.match(/\btarget="([^"]*)"/)?.[1];Z.push(G?`${z} → #${G}`:z)}return Z}#fX(X){let{action:Z,status:$,ms:Q}=X,z=`reactive ${this.element?.id?`#${this.element.id} `:""}${Z} → ${$??"—"} (${Math.round(Q)}ms)`;if(console.groupCollapsed(z),console.log(`params: [${X.paramNames.join(", ")}] + collected: [${X.fieldNames.join(", ")}]`),console.log(`encoding: ${X.encoding}`),X.streams.length)console.log(`streams: ${X.streams.join(" ")}`);console.log(`token: ${X.tokenRefreshed?"refreshed ✓":"unchanged"}`),console.groupEnd()}async#pX(X,Z,$,Q,j){let{fields:z,files:G}=this.#QX(),q=this.#KX(Z),Y={...z,...q},K=this.#q,W=G.length>0,B=W?this.#KZ(K,X,Y,G):JSON.stringify({token:K,act:X,params:Y}),J=this.#e()?{action:X,paramNames:Object.keys(q),fieldNames:Object.keys(z),encoding:W?"multipart":"json",status:null,streams:[],tokenRefreshed:!1,started:this.#XX()}:null;await this.#yX();try{if(navigator.onLine===!1){this.#Y($),this.#G("offline"),this.#z(X,Z,Y,{kind:"offline"});return}let L;try{let V={Accept:"text/vnd.turbo-stream.html","X-CSRF-Token":this.#xZ()};if(!W)V["Content-Type"]="application/json";let O=this.#PZ();if(O)V["X-Pgbus-Connection"]=O;L=await fetch(this.#AZ(),{method:"POST",headers:V,body:B,credentials:"same-origin",signal:AbortSignal.timeout(this.#OZ())})}catch(V){if(console.error("[phlex-reactive] action error",V),this.#Y($),V?.name==="TimeoutError"||V?.name==="AbortError"){this.#G("timeout"),this.#z(X,Z,Y,{kind:"timeout"});return}this.#hX(),this.#G("network"),this.#z(X,Z,Y,{kind:"network"});return}if(J)J.status=L.status;if(L.redirected){console.error("[phlex-reactive] action was redirected (auth/CSRF?) — no update applied"),this.#Y($),this.#G("redirected"),this.#z(X,Z,Y,{kind:"redirected",status:L.status});return}if(!L.ok){let V=await L.text();if(console.error(`[phlex-reactive] action failed: HTTP ${L.status}`,V),this.#Y($),(L.headers.get("Content-Type")||"").includes("turbo-stream")){let O=this.#$X(V);if(this.#q=O??this.#q,J)this.#ZX(J,V,O);window.Turbo.renderStreamMessage(V)}this.#G("http"),this.#z(X,Z,Y,{kind:"http",status:L.status,body:V});return}let H=L.headers.get("Content-Type")||"";if(!H.includes("turbo-stream")){console.error(`[phlex-reactive] expected a turbo-stream, got "${H}" — no update applied`),this.#Y($),this.#G("content-type"),this.#z(X,Z,Y,{kind:"content-type",status:L.status});return}let U=await L.text(),A=this.#$X(U);if(this.#q=A??this.#q,J)this.#ZX(J,U,A);if(window.Turbo.renderStreamMessage(U),j)queueMicrotask(j);this.#wX(),this.#D("reactive:applied",{action:X,params:Y,html:U})}catch(L){console.error("[phlex-reactive] action error",L),this.#Y($),this.#D("reactive:error",{action:X,params:Y,kind:"apply"})}finally{if(Q?.(),J)this.#fX({...J,ms:this.#XX()-J.started})}}#ZX(X,Z,$){X.streams=this.#vX(Z),X.tokenRefreshed=$!=null}get#q(){return this.#m??this.tokenValue}set#q(X){this.#m=X}#$X(X){let Z=this.element.id;if(!Z)return X.match(/data-reactive-token-value="([^"]+)"/)?.[1];let{token:$,self:Q}=this.#mX(Z),j=X.match($);if(j)return j[1];let z=X.match(Q);if(z)return z[1].match(/data-reactive-token-value="([^"]+)"/)?.[1];return}#mX(X){let Z=this.#u;if(Z&&Z.id===X)return Z;let $=gX(X);return this.#u={id:X,token:new RegExp(`<turbo-stream\\b[^>]*\\baction="reactive:token"[^>]*\\btarget="${$}"[^>]*\\bdata-reactive-token-value="([^"]+)"`),self:new RegExp(`<turbo-stream\\b[^>]*\\baction="(?:replace|update)"[^>]*\\btarget="${$}"[^>]*>([\\s\\S]*?)</turbo-stream>`)}}#$(X){return X.closest('[data-controller~="reactive"]')===this.element}#X(){if(this.element.querySelectorAll('[data-controller~="reactive"]').length===0)return()=>!0;return(Z)=>this.#$(Z)}#w(X,Z){if(X)return X;if(!Z)return null;let $=Z;if(typeof Z==="string")try{$=JSON.parse(Z)}catch{return console.warn(`[phlex-reactive] malformed conditional confirm payload ${JSON.stringify(Z)} — skipped`),null}if(!$||typeof $!=="object")return null;let{fields:Q}=this.#QX(),j=(G)=>Q[G],z;if(typeof $.predicate==="string"){let G=LX($.predicate);if(!G)return console.warn(`[phlex-reactive] confirm predicate "${$.predicate}" is not registered — proceeding without a dialog (register it with setConfirmPredicate)`),null;z=!!G(Q)}else z=v($.groups?.any,j)===!0;return z?$.message:null}#QX(){let X={},Z=[],$=this.#X();return this.element.querySelectorAll("input[name], select[name], textarea[name]").forEach((Q)=>{if(!$(Q))return;if(Q.type==="file")for(let j of Q.files??[])Z.push({name:Q.name,file:j,multiple:Q.multiple});else if(Q.type==="checkbox")X[Q.name]=Q.checked;else if(Q.type==="radio"){if(Q.checked)X[Q.name]=Q.value}else X[Q.name]=Q.value}),this.element.querySelectorAll("[name]:is(lexxy-editor, trix-editor, [contenteditable=''], [contenteditable=true], [contenteditable=plaintext-only])").forEach((Q)=>{if(!$(Q))return;let j=Q.getAttribute("name");if(!j)return;let z=X[j];if(z==null||z==="")X[j]=Q.value??Q.textContent??Q.innerHTML??""}),{fields:X,files:Z}}#h(){if(typeof this.element?.querySelectorAll!=="function")return;let X=0;if(this.element.querySelectorAll("input[name], select[name], textarea[name]").forEach((Z)=>{if(!this.#$(Z))return;if(Z.type==="file")return;if(this.#uX(Z))Z.setAttribute("data-reactive-dirty","true"),X++;else Z.removeAttribute("data-reactive-dirty")}),X>0)this.element.setAttribute("data-reactive-dirty",String(X));else this.element.removeAttribute("data-reactive-dirty")}#uX(X){if(X.type==="checkbox"||X.type==="radio")return X.checked!==X.defaultChecked;if(X.tag==="select"||X.options)return Array.from(X.options??[]).some((Z)=>Z.selected!==Z.defaultSelected);return X.value!==X.defaultValue}#jX(){let X=this.element.getAttribute?.("data-reactive-dirty"),Z=Number(X);return Number.isFinite(Z)&&Z>0?Z:0}#gX(){if(typeof window>"u"||typeof window.addEventListener!=="function")return;this.#J=(X)=>{if(this.#jX()===0)return;return X.preventDefault(),X.returnValue="You have unsaved changes.",X.returnValue},this.#L=(X)=>{if(this.#jX()===0)return;if(!(typeof window.confirm==="function"?window.confirm("You have unsaved changes. Leave anyway?"):!0))X.preventDefault?.()},window.addEventListener("beforeunload",this.#J),window.addEventListener("turbo:before-visit",this.#L)}#cX(){if(this.#W)this.element.removeEventListener?.("turbo:morph-element",this.#W),this.#W=void 0;if(typeof window<"u"&&typeof window.removeEventListener==="function"){if(this.#J)window.removeEventListener("beforeunload",this.#J);if(this.#L)window.removeEventListener("turbo:before-visit",this.#L)}this.#J=void 0,this.#L=void 0}#dX(){if(this.element.getAttribute?.("data-reactive-show-targets"))return!0;let X=this.element.querySelectorAll?.(i)??[];for(let Z of X)if(this.#$(Z))return!0;return!1}#zX(){if(typeof this.element?.querySelectorAll!=="function")return;let X=this.#X(),Z=this.element.getAttribute?.("data-reactive-scope")||null,$=new Map,Q=(j)=>{if(!$.has(j))$.set(j,this.#sX(j,X,Z));return $.get(j)};for(let j of this.element.querySelectorAll(i)){if(!X(j))continue;let z=j.getAttribute("data-reactive-show");if(z!==null){let K=rX(nX(z),Q);if(K!==null)this.#GX(j,K,X,Z);continue}let G=j.getAttribute("data-reactive-show-field");if(!G)continue;let q=Q(G);if(q===null)continue;let Y=sX(j,q);if(Y===null)continue;this.#GX(j,Y,X,Z)}this.#lX(Q)}#GX(X,Z,$,Q){if(X.hidden=!Z,X.getAttribute("data-reactive-show-disable")!=="true")return;if(typeof X.querySelectorAll!=="function")return;for(let j of X.querySelectorAll("input[name], select[name], textarea[name]"))if($(j))j.disabled=!Z;if(X.name&&$(X))X.disabled=!Z}#lX(X){let Z=this.#oX();for(let[$,Q]of Object.entries(Z)){if($.startsWith("#")){this.#iX($,Q,X);continue}if(!Q||typeof Q!=="object"||Array.isArray(Q))continue;let j=X($);if(j===null)continue;let z=()=>j;for(let[G,q]of Object.entries(Q)){if(!o(G))continue;let Y;if(Array.isArray(q)){if(q.length===0){console.warn(`[phlex-reactive] malformed reactive_show_targets group for ${G} — skipped`);continue}Y=q.every((K)=>u(K,z))}else{let K=KX(q,j);if(K===null){console.warn(`[phlex-reactive] malformed reactive_show_targets predicate for ${G} — skipped`);continue}Y=K}for(let K of document.querySelectorAll(G))K.hidden=!Y}}}#iX(X,Z,$){if(!o(X))return;let Q=Z&&typeof Z==="object"&&!Array.isArray(Z)?Z.any:null,j=aX(Q);if(j===null){console.warn(`[phlex-reactive] malformed reactive_show_targets conditions for ${X} — skipped`);return}if(j.every((G)=>$(G)===null))return;let z=v(Q,$);if(z===null)return;for(let G of document.querySelectorAll(X))G.hidden=!z}#oX(){let X=this.element.getAttribute?.("data-reactive-show-targets");if(!X)return{};try{let Z=JSON.parse(X);if(Z&&typeof Z==="object"&&!Array.isArray(Z))return Z}catch{}return console.warn("[phlex-reactive] malformed data-reactive-show-targets — ignored. "+"Did two reactive_show_targets calls collide on one root? Declare every field in ONE call: reactive_show_targets(mode: { ... }, kind: { ... })"),{}}#sX(X,Z,$){let Q=$&&!X.includes("[")?`${$}[${X}]`:X,j=!1,z=null;for(let G of this.element.querySelectorAll(`[name="${Q}"]`)){if(!Z(G))continue;if(G.type==="checkbox")return G.checked?"true":"false";if(G.type==="radio"){if(G.checked)return G.value??"";j=!0;continue}z??=G}if(z)return z.value??"";return j?"":null}#nX(){if(!this.#Z)return;this.element.removeEventListener?.("input",this.#Z),this.element.removeEventListener?.("change",this.#Z),this.element.removeEventListener?.("turbo:morph-element",this.#Z),this.#Z=void 0}#R(){return!!(this.element.getAttribute?.("data-reactive-filter-input")&&this.element.getAttribute?.("data-reactive-filter-option"))}#aX(){return this.element.getAttribute?.("data-reactive-compute-seed")==="true"}#rX(X){let Z=this.element.getAttribute("data-reactive-filter-input");return!!Z&&typeof X.target?.matches==="function"&&X.target.matches(Z)}#_(){if(typeof this.element?.querySelectorAll!=="function")return;let X=this.element.getAttribute("data-reactive-filter-input"),Z=this.element.getAttribute("data-reactive-filter-option");if(!X||!Z)return;let $=this.#X(),Q=[...this.element.querySelectorAll(X)].find($);if(!Q)return;let j=(Q.value??"").trim().toLowerCase(),z=0;for(let Y of this.element.querySelectorAll(Z)){if(!$(Y))continue;let K=(Y.getAttribute("data-reactive-filter-text")??Y.textContent??"").toLowerCase(),W=Y.hasAttribute?.("data-reactive-tags-selected")||j!==""&&!K.includes(j);if(Y.hidden=W,W)Y.removeAttribute("data-reactive-highlighted");else z++}let G=this.element.getAttribute("data-reactive-filter-group");if(G)for(let Y of this.element.querySelectorAll(G)){if(!$(Y))continue;let K=[...Y.querySelectorAll(Z)].filter($);if(K.length===0)continue;Y.hidden=K.every((W)=>W.hidden)}let q=this.element.getAttribute("data-reactive-filter-empty");if(q){for(let Y of this.element.querySelectorAll(q))if($(Y))Y.hidden=z>0}}#tX(){if(!this.#Q)return;this.element.removeEventListener?.("input",this.#Q),this.element.removeEventListener?.("turbo:morph-element",this.#Q),this.#Q=void 0}#F(){return!!this.element.getAttribute?.("data-reactive-tags-field")}#eX(){if(typeof this.element?.querySelector!=="function")return!1;return!!this.element.querySelector("[data-reactive-nested-json]")}#y(){if(typeof this.element?.querySelectorAll!=="function")return null;let X=this.element.getAttribute("data-reactive-tags-field");if(!X)return null;let Z=this.#X();return[...this.element.querySelectorAll(X)].find(Z)??null}#v(X){let Z=new Set,$=[];for(let Q of String(X.value??"").split(",")){let j=Q.trim();if(j===""||Z.has(j.toLowerCase()))continue;Z.add(j.toLowerCase()),$.push(j)}return $}#qX(X){let Z=this.#y();if(!Z)return!1;let $=this.#v(Z),Q=new Set($.map((z)=>z.toLowerCase())),j=!1;for(let z of X){let G=String(z??"").trim();if(G===""||Q.has(G.toLowerCase()))continue;Q.add(G.toLowerCase()),$.push(G),j=!0}if(j)this.#YX(Z,$);return j}#YX(X,Z){if(X.value=Z.join(","),typeof X.dispatchEvent==="function")X.dispatchEvent(new Event("input",{bubbles:!0}));this.#f()}#XZ(){if(typeof this.element?.querySelectorAll!=="function")return null;let X=this.element.getAttribute("data-reactive-filter-input");if(!X)return null;let Z=this.#X();return[...this.element.querySelectorAll(X)].find(Z)??null}#f(){let X=this.#y();if(!X)return;let Z=this.#v(X),$=this.#X();this.#ZZ(Z,$),this.#$Z(Z,$)}#ZZ(X,Z){let $=[...this.element.querySelectorAll("[data-reactive-tags-list]")].find(Z);if(!$)return;let j=[...this.element.querySelectorAll("[data-reactive-tags-template]")].find(Z)?.content?.firstElementChild;if(!j){if(!this.#c)console.warn("[phlex-reactive] reactive_tags: no chip <template data-reactive-tags-template> found in this root — "+"chips will not render (the hidden field still updates). Add a template with a [data-reactive-tag-text] node and a reactive_tags_remove button."),this.#c=!0;return}while($.firstChild)$.removeChild($.firstChild);for(let z of X){let G=j.cloneNode(!0);G.setAttribute?.("data-reactive-tag",z);let q=G.matches?.("[data-reactive-tag-text]")?G:(G.querySelectorAll?.("[data-reactive-tag-text]")??[])[0];if(q)q.textContent=z;let Y=[...G.querySelectorAll?.('[data-action*="reactive#tagsRemove"]')??[]];if(G.matches?.('[data-action*="reactive#tagsRemove"]'))Y.push(G);for(let K of Y)K.setAttribute?.("data-reactive-tag-param",z);$.appendChild(G)}}#$Z(X,Z){let $=new Set(X.map((Q)=>Q.toLowerCase()));for(let Q of this.element.querySelectorAll("[role=option]")){if(!Z(Q))continue;let j=Q.getAttribute?.("data-reactive-tag-param");if(!j)continue;if($.has(j.toLowerCase()))Q.setAttribute("data-reactive-tags-selected","true"),Q.hidden=!0,Q.removeAttribute?.("data-reactive-highlighted");else if(Q.hasAttribute?.("data-reactive-tags-selected")){if(Q.removeAttribute("data-reactive-tags-selected"),!this.#R())Q.hidden=!1}}if(this.#R())this.#_()}#QZ(){return this.#k=Math.max(this.#k+1,Date.now()),this.#k}#jZ(X,Z){let $=[X,...X.querySelectorAll?.("*")??[]];for(let Q of $)for(let j of["name","id","for"]){let z=Q.getAttribute?.(j);if(z&&z.includes("NEW_ROW"))Q.setAttribute?.(j,z.replaceAll("NEW_ROW",String(Z)))}}#zZ(X){if(this.#d)return;console.warn(`[phlex-reactive] nested rows: no owned [data-reactive-nested-list="${X}"] container + <template data-reactive-nested-template="${X}"> pair found in this root — the add `+"trigger did nothing. Render both inside the same reactive root (reactive_nested_list / reactive_nested_template)."),this.#d=!0}#GZ(){if(!this.#V)return;this.element.removeEventListener?.("turbo:morph-element",this.#V),this.#V=void 0}#qZ(){if(this.#j)this.element.removeEventListener?.("input",this.#j),this.element.removeEventListener?.("change",this.#j),this.#j=void 0;if(this.#B)this.element.removeEventListener?.("turbo:morph-element",this.#B),this.#B=void 0}#YZ(){if(!this.#M)return;this.element.removeEventListener?.("turbo:morph-element",this.#M),this.#M=void 0}#KZ(X,Z,$,Q){let j=new FormData;j.append("token",X),j.append("act",Z);for(let[G,q]of Object.entries($))this.#p(j,`params[${G}]`,q);let z=this.#HZ(Q);for(let{name:G,file:q,multiple:Y}of Q){let W=Y||z.has(G)?`params[${G}][]`:`params[${G}]`;j.append(W,q,q.name)}return j}#p(X,Z,$){if($==null)X.append(Z,"");else if(Array.isArray($))$.forEach((Q,j)=>this.#p(X,`${Z}[${j}]`,Q));else if(typeof $==="object")for(let[Q,j]of Object.entries($))this.#p(X,`${Z}[${Q}]`,j);else X.append(Z,String($))}#HZ(X){let Z=new Map;for(let{name:$}of X)Z.set($,(Z.get($)??0)+1);return new Set([...Z].filter(([,$])=>$>1).map(([$])=>$))}#KX(X){if(!X)return{};try{return typeof X==="string"?JSON.parse(X):X}catch{return{}}}#HX(X){return HX(X)}#UX(X){UX(X,(Z)=>this.#WX(Z))}#WX(X){let Z=X.to;if(Z==="@root")return[this.element];if(typeof Z!=="string"||Z==="")return[];if(X.global)return[...document.querySelectorAll(Z)];return[...this.element.querySelectorAll(Z)].filter(($)=>this.#$($))}#UZ(X,Z){if(X?.checked!=="keep")return!1;let $=Z?.type;return $==="checkbox"||$==="radio"}#WZ(X,Z){if(!X)return null;let $=this.#JX(X,Z,!0);return $.length?$:null}#Y(X){if(!X)return;if(!this.element.isConnected)return;for(let Z of X)Z()}#JZ(X,Z,$){this.#VZ(X,Z);let Q=$?this.#JX($,Z,!1):[];QX();let j=!1;return()=>{if(j)return;j=!0,this.#BZ(X,Z),jX();for(let z of Q)z()}}#JX(X,Z,$){let Q=[];for(let j of this.#LX(X,Z)){if(X.add_class){let z=X.add_class.filter((G)=>!j.classList.contains(G));if(j.classList.add(...z),z.length)Q.push(()=>j.classList.remove(...z))}if(X.remove_class){let z=X.remove_class.filter((G)=>j.classList.contains(G));if(j.classList.remove(...z),z.length)Q.push(()=>j.classList.add(...z))}if(X.toggle_class)X.toggle_class.forEach((z)=>j.classList.toggle(z)),Q.push(()=>X.toggle_class.forEach((z)=>j.classList.toggle(z)));if(X.hide)j.hidden=!0,Q.push(()=>j.hidden=!1);if(X.show)j.hidden=!1,Q.push(()=>j.hidden=!0)}if(Z&&(X.disable||X.text!=null))Q.push(this.#LZ(X,Z));if($&&X.checked==="keep"&&Z&&"checked"in Z){let j=Z.checked;Q.push(()=>Z.checked=!j)}return Q}#LX(X,Z){if(X.to==null)return Z?[Z]:[];return this.#WX({to:X.to})}#LZ(X,Z){let $=this.#O.get(Z);if($)$.count++;else this.#O.set(Z,{count:1,disabled:Z.disabled,html:Z.innerHTML,hadText:X.text!=null,swappedTo:X.text});if(X.disable)Z.disabled=!0;if(X.text!=null)Z.innerHTML=X.text;return()=>this.#MZ(Z,X)}#VZ(X,Z){if(this.#K(Z,X,1),this.#K(this.element,X,1),this.#U.set(X,(this.#U.get(X)??0)+1),this.#T++===0)this.element.setAttribute("aria-busy","true");for(let $ of this.#VX(X))this.#K($,X,1)}#BZ(X,Z){this.#K(Z,X,-1),this.#K(this.element,X,-1);let $=(this.#U.get(X)??1)-1;if($<=0)this.#U.delete(X);else this.#U.set(X,$);if(--this.#T<=0)this.#T=0,this.element.removeAttribute("aria-busy");for(let Q of this.#VX(X))this.#K(Q,X,-1)}#K(X,Z,$){if(!X||typeof X.getAttribute!=="function")return;let Q=this.#E.get(X)??new Map,j=(Q.get(Z)??0)+$;if(j<=0)Q.delete(Z);else Q.set(Z,j);if(Q.size===0){this.#E.delete(X),X.removeAttribute("data-reactive-busy");return}this.#E.set(X,Q),X.setAttribute("data-reactive-busy",[...Q.keys()].join(" "))}#VX(X){return[...this.element.querySelectorAll?.("[data-reactive-busy-on]")??[]].filter(($)=>$.getAttribute("data-reactive-busy-on")===X&&this.#$($))}#MZ(X,Z){let $=this.#O.get(X);if(!$)return;if(--$.count>0)return;if(this.#O.delete(X),!X.isConnected)return;if(Z.disable)X.disabled=$.disabled;if($.hadText&&X.innerHTML===$.swappedTo)X.innerHTML=$.html}#_Z(X){if(!X||typeof X!=="object")return null;let{class:Z,...$}=X;return Z==null?X:{...$,add_class:Z}}#AZ(){return this.#BX??=document.querySelector('meta[name="phlex-reactive-action-path"]')?.content||"/reactive/actions"}#OZ(){if(this.#I!=null)return this.#I;let X=document.querySelector('meta[name="phlex-reactive-timeout"]')?.content,Z=Number(X);return this.#I=Number.isFinite(Z)&&Z>0?Z:30000}#xZ(){return document.querySelector('meta[name="csrf-token"]')?.content??""}#PZ(){return document.querySelector("pgbus-stream-source[connection-id]")?.getAttribute("connection-id")||document.querySelector('meta[name="pgbus-connection-id"]')?.content||null}}export{GZ as resetReactiveDefers,JZ as resetReactiveActivity,VX as registerReactiveVisit,BX as registerReactiveToken,fX as registerReactiveOffline,MX as registerReactiveJs,IX as registerReactiveEffects,DX as registerReactiveDismiss,_X as registerReactiveDefer,d as registerReactiveActions,WZ as reactiveActivityCount,qZ as pendingDeferVia,jX as exitReactiveActivity,gX as escapeRegExp,QX as enterReactiveActivity,pX as enableLatencySim,mX as disableLatencySim,BZ as default,cX as checkReactiveRegistration,LZ as __resetReactiveRegistrationForTest,HZ as __resetReactiveOfflineForTest,UZ as __resetReactiveLatencyForTest,KZ as __resetReactiveEffectsForTest,YZ as __resetReactiveDismissForTest,VZ as __markReactiveConnectedForTest,m as LATENCY_KEY,$X as ACTIVE_ATTR};
1
+ import{Controller as WX}from"@hotwired/stimulus";import{confirmResolver as R}from"phlex/reactive/confirm";import{computeReducer as JX}from"phlex/reactive/compute";import{confirmPredicate as LX}from"phlex/reactive/confirm_predicate";function VX(){let X=window.Turbo?.StreamActions;if(!X||X["reactive:visit"])return;X["reactive:visit"]=function(){let Z=this.getAttribute("data-url");if(Z)window.Turbo.visit(Z,{action:"advance"})}}function BX(){let X=window.Turbo?.StreamActions;if(!X||X["reactive:token"])return;X["reactive:token"]=function(){let Z=this.getAttribute("data-reactive-token-value"),$=this.getAttribute("target");if(!Z||!$)return;let Q=document.getElementById($);if(Q)Q.setAttribute("data-reactive-token-value",Z)}}function _X(){let X=window.Turbo?.StreamActions;if(!X||X["reactive:js"])return;X["reactive:js"]=function(){let Z=HX(this.getAttribute("data-reactive-ops"));if(!Z.length)return;let $=this.getAttribute("target"),Q=$?document.getElementById($):null;if($&&!Q)return;UX(Z,(j)=>ZZ(j,Q))}}var _=new Map;function s(X,Z){let $=!_.has(X);if(_.set(X,Z),$)QX()}function N(X){if(_.delete(X))jX()}function GZ(){_.clear(),S=!1}function qZ(X){return _.get(X)?.via}var S=!1;function AX(){let X=window.Turbo?.StreamActions;if(!X||X["reactive:defer"])return;if(X["reactive:defer"]=function(){let Z=this.getAttribute("target");if(!Z)return;if(this.getAttribute("data-reactive-defer-via")==="stream"){OX(Z,this);return}let $=this.getAttribute("data-reactive-defer-token");if(!$)return;C(Z,$)},!S&&typeof document<"u"&&document.addEventListener)S=!0,document.addEventListener("turbo:before-stream-render",MX)}function MX(X){let $=X.target?.getAttribute?.("target");if(!$)return;let Q=$.startsWith("reactive-defer-src-")?$.slice(19):$;if(_.get(Q)?.via==="stream")N(Q)}function C(X,Z){let $=document.getElementById(X);if(!$){console.warn(`[phlex-reactive] reactive:defer target #${X} is not on the page — skipped`);return}a(X),r($);let Q={via:"fetch",abort:new AbortController,timedOut:!1};s(X,Q),xX(X,Q,Z)}function OX(X,Z){let $=document.getElementById(X);if(!$){console.warn(`[phlex-reactive] reactive:defer target #${X} is not on the page — skipped`);return}let Q=Z.getAttribute("data-reactive-defer-src");if(!Q)return;if(!globalThis.customElements?.get?.("pgbus-stream-source")){let z=Z.getAttribute("data-reactive-defer-token");if(z){C(X,z);return}console.error("[phlex-reactive] reactive:defer via=stream but <pgbus-stream-source> is not registered "+"and no fallback token was provided — is the pgbus client loaded on this page?");return}a(X),r($);let j=document.createElement("pgbus-stream-source");j.id=n(X),j.setAttribute("src",Q),j.setAttribute("since-id",Z.getAttribute("data-reactive-defer-since-id")??"0"),j.setAttribute("hidden",""),document.body.appendChild(j),s(X,{via:"stream"})}function n(X){return`reactive-defer-src-${X}`}async function xX(X,Z,$){let Q=setTimeout(()=>{Z.timedOut=!0,Z.abort.abort()},CX()),j;try{j=await fetch(PX(),{method:"POST",headers:{Accept:"text/vnd.turbo-stream.html","Content-Type":"application/json","X-CSRF-Token":NX()},body:JSON.stringify({token:$}),credentials:"same-origin",signal:Z.abort.signal})}catch(G){if(clearTimeout(Q),_.get(X)!==Z)return;console.error("[phlex-reactive] deferred render failed",G),F(X,$);return}if(_.get(X)!==Z){clearTimeout(Q);return}if(j.status===204){clearTimeout(Q),g(X);return}if(!j.ok){clearTimeout(Q),console.error(`[phlex-reactive] deferred render failed: HTTP ${j.status}`),F(X,$,j.status);return}let z;try{z=await j.text()}catch(G){if(clearTimeout(Q),_.get(X)!==Z)return;console.error("[phlex-reactive] deferred render failed reading the body",G),F(X,$);return}if(clearTimeout(Q),_.get(X)!==Z)return;g(X),window.Turbo.renderStreamMessage(z)}function a(X){let Z=_.get(X);if(!Z)return;if(N(X),Z.via==="fetch")Z.abort.abort();else document.getElementById(n(X))?.remove?.()}function r(X){X.setAttribute("data-reactive-defer-pending","true"),X.setAttribute("aria-busy","true")}function t(X){X.removeAttribute("data-reactive-defer-pending"),X.removeAttribute("aria-busy")}function g(X){N(X);let Z=document.getElementById(X);if(!Z)return;t(Z),Z.removeAttribute("data-reactive-error")}function F(X,Z,$){N(X);let Q=document.getElementById(X);if(!Q)return;t(Q),Q.setAttribute("data-reactive-error","defer");let j=()=>{let z=document.getElementById(X);if(!z){console.warn("[phlex-reactive] defer retry() ignored — the target left the DOM");return}z.removeAttribute("data-reactive-error"),C(X,Z)};Q.dispatchEvent(new CustomEvent("reactive:error",{bubbles:!0,composed:!0,detail:{kind:"defer",target:X,status:$,retry:j}}))}function PX(){return document.querySelector('meta[name="phlex-reactive-defer-path"]')?.content||"/reactive/defer"}function NX(){return document.querySelector('meta[name="csrf-token"]')?.content??""}function CX(){let X=document.querySelector('meta[name="phlex-reactive-timeout"]')?.content,Z=Number(X);return Number.isFinite(Z)&&Z>0?Z:30000}var b=!1;function DX(){if(b)return;if(typeof document>"u"||!document.addEventListener)return;b=!0,document.addEventListener("turbo:before-stream-render",RX)}function RX(X){let Z=X.detail,$=Z?.render;if(typeof $!=="function"||$.__reactiveDismissWrapped){if(typeof requestAnimationFrame==="function")requestAnimationFrame(I);else setTimeout(I,0);return}let Q=async(j)=>{await $(j),I()};Q.__reactiveDismissWrapped=!0,Z.render=Q}function I(){let X=document.querySelectorAll("[data-reactive-dismiss-after]");for(let Z of X){if(Z.hasAttribute("data-reactive-dismiss-scheduled"))continue;let $=Number(Z.getAttribute("data-reactive-dismiss-after"));if(!Number.isFinite($)||$<=0)continue;Z.setAttribute("data-reactive-dismiss-scheduled",""),setTimeout(()=>Z.remove(),$)}}function YZ(){b=!1}var FX=Object.freeze({append:"enter",prepend:"enter",replace:"update",update:"update",remove:"exit"}),T=Object.freeze(["fade","slide","scale","highlight","shake"]),w="data-reactive-fx-pending",e=1000,h=!1;function IX(){if(h)return;if(typeof document>"u"||typeof document.addEventListener!=="function")return;h=!0,document.addEventListener("turbo:before-stream-render",TX)}function KZ(){h=!1}function TX(X){let Z=X.detail,$=Z?.render;if(typeof $!=="function"||$.__reactiveEffectsWrapped)return;let Q=Z?.newStream??X.target,j=FX[Q?.getAttribute?.("action")];if(!j||SX())return;let z=EX(Q,j);if(!z)return;let G=j==="exit"?async(q)=>{await hX(x(Q),z),await $(q)}:async(q)=>{let Y=j==="enter"?bX(Q):null;if(await $(q),j==="enter")wX(Y,z);else XX(x(Q),z)};G.__reactiveEffectsWrapped=!0,Z.render=G}function EX(X,Z){let $=X.getAttribute?.("data-reactive-effect");if($==="off")return null;if($)return c($,Z);let j=(Z==="enter"?kX(X):x(X))?.getAttribute?.(`data-reactive-effect-${Z}`);return j?c(j,Z):null}function x(X){let Z=X.getAttribute?.("target");return Z?document.getElementById?.(Z)??null:null}function kX(X){return X.querySelector?.("template")?.content?.firstElementChild??null}function c(X,Z){if(X.startsWith("[")){let Q=null;try{let j=JSON.parse(X);if(Array.isArray(j)&&j.length===3)Q=j.map(String)}catch{}if(Q)return{legs:Q};return console.warn(`[phlex-reactive] malformed effect legs ${JSON.stringify(X)} — skipped`),null}let $=X==="random"?T[Math.floor(Math.random()*T.length)]:X;if(!T.includes($))return console.warn(`[phlex-reactive] unknown effect ${JSON.stringify(X)} — skipped`),null;return{className:`reactive-fx--${$}-${Z}`}}function SX(){try{return typeof matchMedia==="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches}catch{return!1}}function bX(X){let Z=X.querySelector?.("template")?.content;if(!Z)return null;for(let $ of Array.from(Z.children??[]))$.setAttribute?.(w,"");return x(X)}function wX(X,Z){if(typeof X?.querySelectorAll!=="function")return;for(let $ of Array.from(X.querySelectorAll(`[${w}]`)))$.removeAttribute(w),XX($,Z)}async function hX(X,Z){if(!X?.classList)return;if(Z.legs){await ZX(X,Z.legs);return}X.classList.add(Z.className);let $=p(X);if($<=0){X.classList.remove(Z.className);return}await f(X,$),X.classList.remove(Z.className)}function XX(X,Z){if(!X?.classList)return;if(Z.legs){ZX(X,Z.legs);return}if(X.classList.contains(Z.className))X.classList.remove(Z.className),X.offsetWidth;X.classList.add(Z.className);let $=p(X);if($<=0){X.classList.remove(Z.className);return}let Q=X.__reactiveFxToken=(X.__reactiveFxToken??0)+1;f(X,$).then(()=>{if(X.__reactiveFxToken===Q)X.classList.remove(Z.className)})}async function ZX(X,Z){let[$,Q,j]=Z.map(yX),z=X.__reactiveFxToken=(X.__reactiveFxToken??0)+1;if(X.classList.remove(...$,...Q,...j),X.classList.add(...$,...Q),await vX(),X.__reactiveFxToken!==z)return;X.classList.remove(...Q),X.classList.add(...j);let G=p(X);if(G>0)await f(X,G);if(X.__reactiveFxToken!==z)return;X.classList.remove(...$,...j)}function yX(X){return String(X??"").split(/\s+/).filter(Boolean)}function p(X){if(typeof getComputedStyle!=="function")return 0;try{let Z=getComputedStyle(X),$=(z)=>String(z??"").split(",").reduce((G,q)=>Math.max(G,parseFloat(q)||0),0),Q=$(Z.animationDuration)+$(Z.animationDelay),j=$(Z.transitionDuration)+$(Z.transitionDelay);return Math.min(Math.max(Q,j)*1000,e)}catch{return 0}}function f(X,Z){return new Promise(($)=>{let Q=!1,j=()=>{if(Q)return;Q=!0,$()};X.addEventListener?.("animationend",j,{once:!0}),X.addEventListener?.("transitionend",j,{once:!0}),setTimeout(j,Math.min(Z+50,e))})}function vX(){return new Promise((X)=>{if(typeof requestAnimationFrame==="function")requestAnimationFrame(()=>X());else setTimeout(X,16)})}var y=!1;function pX(){if(y)return;if(typeof window>"u"||typeof document>"u")return;if(typeof window.addEventListener!=="function")return;y=!0;let X=()=>{let Z=document.documentElement;if(typeof Z?.toggleAttribute!=="function")return;Z.toggleAttribute("data-reactive-offline",globalThis.navigator?.onLine===!1)};X(),window.addEventListener("online",X),window.addEventListener("offline",X)}function HZ(){y=!1}var m="phlex-reactive:latency",P=!1;function fX(X){if(typeof sessionStorage>"u")return;sessionStorage.setItem(m,String(X))}function mX(){if(typeof sessionStorage>"u")return;sessionStorage.removeItem(m),P=!1}function uX(){if(typeof window>"u"||typeof document>"u")return;if(document.querySelector?.('meta[name="phlex-reactive-env"]')?.content!=="development")return;window.PhlexReactive={enableLatencySim:fX,disableLatencySim:mX}}function UZ(){P=!1}var $X="data-reactive-active",A=0;function QX(){if(A++,A===1)zX("reactive:busy")}function jX(){if(A===0)return;if(A--,A===0)zX("reactive:idle")}function WZ(){return A}function JZ(){A=0,(typeof document<"u"?document.documentElement:null)?.removeAttribute?.($X)}function zX(X){if(typeof document>"u")return;let Z=document.documentElement;if(typeof Z?.toggleAttribute==="function")Z.toggleAttribute($X,A>0);if(typeof document.dispatchEvent==="function"&&typeof CustomEvent==="function")document.dispatchEvent(new CustomEvent(X,{detail:{count:A}}))}function d(){VX(),BX(),_X(),AX(),DX(),IX(),pX(),uX()}function gX(X){return X.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}if(typeof window<"u")if(window.Turbo)d();else document.addEventListener("turbo:load",d,{once:!0});var D=!1;function cX(){if(D)return;if(typeof document>"u")return;let X=document.querySelectorAll('[data-controller~="reactive"]');if(!X||X.length===0)return;console.warn("[phlex-reactive] found "+X.length+' element(s) with data-controller="reactive" '+"but the reactive controller never connected. It is loaded but not registered — "+'add `application.register("reactive", ReactiveController)` (importmap) or import it into app/javascript/controllers/ for lazyLoadControllersFrom apps. See the README.')}function LZ(){D=!1}function VZ(){D=!0}if(typeof window<"u"&&typeof document<"u"){let X=()=>setTimeout(cX,0);if(document.readyState==="loading")document.addEventListener("DOMContentLoaded",X,{once:!0});else X()}var dX=new Set(["href","src","srcdoc","action","formaction","xlink:href","style"]);function lX(X){let Z=String(X).toLowerCase();return Z.startsWith("on")||dX.has(Z)}function iX(X,Z,$){let[Q,j,z]=Z;X.classList.add(Q,j),$(),requestAnimationFrame(()=>{X.classList.remove(j),X.classList.add(z)});let G=!1,q=()=>{if(G)return;G=!0,X.classList.remove(Q,z)};X.addEventListener("animationend",q,{once:!0}),setTimeout(q,350)}var l=Object.freeze({show:(X,Z)=>E(X,!1,Z),hide:(X,Z)=>E(X,!0,Z),toggle:(X,Z)=>E(X,!X.hidden,Z),add_class:(X,Z)=>X.classList.add(...Z.classes??[]),remove_class:(X,Z)=>X.classList.remove(...Z.classes??[]),toggle_class:(X,Z)=>(Z.classes??[]).forEach(($)=>X.classList.toggle($)),set_attr:(X,Z)=>{if(k(Z.name))X.setAttribute(Z.name,Z.value??"")},remove_attr:(X,Z)=>{if(k(Z.name))X.removeAttribute(Z.name)},toggle_attr:(X,Z)=>{if(!k(Z.name))return;if(X.hasAttribute(Z.name))X.removeAttribute(Z.name);else X.setAttribute(Z.name,"")},focus:(X)=>X.focus?.(),focus_first:(X)=>XZ(X)?.focus?.(),text:(X,Z)=>{let $=String(Z.value??"");if(X.textContent!==$)X.textContent=$},dispatch:(X,Z)=>{X.dispatchEvent(new CustomEvent(Z.name,{bubbles:!0,composed:!0,detail:Z.detail??{}}))}});function E(X,Z,$){if($?.transition)iX(X,$.transition,()=>X.hidden=Z);else X.hidden=Z}function k(X){if(!lX(X))return!0;return console.warn(`[phlex-reactive] refused client attr op on ${JSON.stringify(X)} — skipped`),!1}var GX=/^#[A-Za-z_][\w-]*$/;function oX(X){if(typeof X==="string"&&GX.test(X))return!0;return console.warn(`[phlex-reactive] refused cross-root mirror target ${JSON.stringify(X)} — skipped`),!1}function sX(X,Z){let $=X.getAttribute("data-reactive-show-equals");if($!==null)return Z===$;let Q=X.getAttribute("data-reactive-show-not");if(Q!==null)return Z!==Q;let j=X.getAttribute("data-reactive-show-in");if(j!==null){try{let z=JSON.parse(j);if(Array.isArray(z))return z.includes(Z)}catch{}return console.warn(`[phlex-reactive] malformed reactive_show in: list ${JSON.stringify(j)} — skipped`),null}for(let z of qX){let G=X.getAttribute(`data-reactive-show-${z}`);if(G!==null)return YX(z,G,Z)}return console.warn("[phlex-reactive] a reactive_show binding declares no predicate — skipped"),null}var qX=["gte","gt","lte","lt"];function YX(X,Z,$){let Q=Number(Z);if(Number.isNaN(Q))return console.warn(`[phlex-reactive] reactive_show ${X}: needs a numeric literal, got ${JSON.stringify(Z)} — skipped`),null;let j=$==null?"":String($).trim(),z=j===""?NaN:Number(j);if(Number.isNaN(z))return!1;switch(X){case"gte":return z>=Q;case"gt":return z>Q;case"lte":return z<=Q;case"lt":return z<Q;default:return null}}function KX(X,Z){if(!X||typeof X!=="object")return null;if(typeof X.equals==="string")return Z===X.equals;if(typeof X.not==="string")return Z!==X.not;if(Array.isArray(X.in))return X.in.includes(Z);for(let $ of qX)if($ in X)return YX($,X[$],Z);return null}var i="[data-reactive-show-field], [data-reactive-show]";function nX(X){try{let Z=JSON.parse(X);if(Z&&typeof Z==="object"&&!Array.isArray(Z))return Z}catch{}return console.warn(`[phlex-reactive] malformed compound reactive_show payload ${JSON.stringify(X)} — skipped`),null}function u(X,Z){if(!X||typeof X!=="object"||typeof X.field!=="string")return!1;let $=Z(X.field)??"";return KX(X,$)===!0}function v(X,Z){if(!Array.isArray(X)||X.length===0)return null;return X.some(($)=>Array.isArray($)&&$.length>0&&$.every((Q)=>u(Q,Z)))}function aX(X){if(!Array.isArray(X)||X.length===0)return null;let Z=new Set;for(let $ of X){if(!Array.isArray($))continue;for(let Q of $)if(Q&&typeof Q==="object"&&typeof Q.field==="string")Z.add(Q.field)}return Z.size>0?[...Z]:null}function rX(X,Z){if(!X||typeof X!=="object")return null;let $=X.any;if(Array.isArray($)&&($.length===0||Array.isArray($[0])))return v($,Z);return tX(X,Z)}function tX(X,Z){let $=Array.isArray(X.all)?"all":Array.isArray(X.any)?"any":null;if(!$)return null;let Q=X[$];if(Q.length===0)return null;let j=Q.map((z)=>u(z,Z));return $==="all"?j.every(Boolean):j.some(Boolean)}function o(X){if(typeof X==="string"&&GX.test(X))return!0;return console.warn(`[phlex-reactive] refused cross-root show target ${JSON.stringify(X)} — skipped`),!1}var eX='a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';function XZ(X){return X.querySelectorAll?.(eX)?.[0]??null}function HX(X){if(Array.isArray(X))return X;if(typeof X!=="string")return[];try{let Z=JSON.parse(X);return Array.isArray(Z)?Z:[]}catch{return[]}}function UX(X,Z){for(let $ of X){if(!Array.isArray($))continue;let[Q,j={}]=$;if(!Object.hasOwn(l,Q)){console.warn(`[phlex-reactive] unknown client op ${JSON.stringify(Q)} — skipped`);continue}for(let z of Z(j))l[Q](z,j)}}function ZZ(X,Z){let $=X.to;if(Z){if($==="@root")return[Z];if(typeof $!=="string"||$==="")return[];if(X.global)return[...document.querySelectorAll($)];return[...Z.querySelectorAll($)]}if(typeof $!=="string"||$===""||$==="@root")return[];return[...document.querySelectorAll($)]}class BZ extends WX{static values={token:String};#m;#M=new Map;#H=new Map;#_X;#I;#u;#T=0;#U=new Map;#E=new WeakMap;#O=new Map;#g=new WeakSet;#W;#J;#L;#Z;#Q;#V;#c=!1;#k=0;#d=!1;#j;#B;#_;#x;connect(){if(D=!0,this.element.id==="")console.warn("[phlex-reactive] a reactive root has no id; its next-action token can't self-match "+"and may fall back to the first token in the response → a silent HTTP 403 on the NEXT action. "+"Put id: on the SAME element as reactive_attrs — use div(**reactive_root) (emits id + token together), "+"or div(id:, **reactive_attrs). The id: must NOT be on a child. See the README.");if(this.element.getAttribute?.("data-reactive-defer-token"))this.#l(),this.#x=()=>this.#l(),this.element.addEventListener?.("turbo:morph-element",this.#x);if(this.#AX()){if(this.#W=()=>this.#h(),this.element.addEventListener?.("turbo:morph-element",this.#W),this.#h(),this.element.getAttribute?.("data-reactive-warn-unsaved")==="true")this.#cX()}if(this.#lX())this.#Z=()=>this.#GX(),this.element.addEventListener?.("input",this.#Z),this.element.addEventListener?.("change",this.#Z),this.element.addEventListener?.("turbo:morph-element",this.#Z),this.#GX();if(this.#R())this.#Q=(X)=>{if(X?.type==="input"&&!this.#tX(X))return;this.#A()},this.element.addEventListener?.("input",this.#Q),this.element.addEventListener?.("turbo:morph-element",this.#Q),this.#A();if(this.#F())this.#V=()=>this.#p(),this.element.addEventListener?.("turbo:morph-element",this.#V),this.#p();if(this.#XZ())this.#j=(X)=>this.syncNestedJson(X),this.#B=()=>this.#N(),this.element.addEventListener?.("input",this.#j),this.element.addEventListener?.("change",this.#j),this.element.addEventListener?.("turbo:morph-element",this.#B),this.#N();if(this.#rX())this.#_=()=>this.recompute(),this.element.addEventListener?.("turbo:morph-element",this.#_),this.recompute()}#AX(){if((this.element.getAttribute?.("data-action")??"").includes("reactive#trackDirty"))return!0;let X=this.element.querySelectorAll?.('[data-action*="reactive#trackDirty"]')??[];for(let Z of X)if(this.#$(Z))return!0;return!1}disconnect(){if(this.#SX(),this.#wX(),this.#dX(),this.#aX(),this.#eX(),this.#qZ(),this.#YZ(),this.#KZ(),this.#x)this.element.removeEventListener?.("turbo:morph-element",this.#x)}#l(){let X=this.element;if(!X?.id)return;let Z=X.getAttribute?.("data-reactive-defer-token");if(!Z)return;if(X.getAttribute?.("data-reactive-defer-pending")!=="true")return;C(X.id,Z)}dispatch(X){let{action:Z,params:$,debounce:Q,throttle:j,confirm:z,confirmWhen:G,outside:q,window:Y,optimistic:K}=X.params;if(!Z)return;let W=X.params.busy??this.#MZ(X.params.loading);if(q&&this.element.contains(X.target))return;let B=X.currentTarget??X.target;if(!Y&&!this.#WZ(K,B))X.preventDefault();let J=this.#w(z,G);if(!J)return this.#e(B,Z,$,Q,j,K,W);Promise.resolve().then(()=>R(J,{el:B})).catch(()=>!1).then((L)=>{if(L)this.#e(B,Z,$,Q,j,K,W)})}runOps(X){let{ops:Z,confirm:$,confirmWhen:Q,outside:j,window:z}=X.params,G=X.currentTarget??X.target;if(j&&this.element.contains(X.target))return;if(!z)X.preventDefault();let q=this.#w($,Q);if(!q)return this.#WX(this.#UX(Z));Promise.resolve().then(()=>R(q,{el:G})).catch(()=>!1).then((Y)=>{if(Y)this.#WX(this.#UX(Z))})}trackDirty(){this.#h()}recompute(X){if(X&&this.#g.has(X))return;let Z=this.#DX(),$=Z.map(([H])=>H),Q=this.element.getAttribute?.("data-reactive-scope")||null,j=(H)=>Q&&!H.includes("[")?`${Q}[${H}]`:H,z=this.#X(),G=new Map,q=(H)=>{if(G.has(H))return G.get(H);let U=null;for(let M of this.element.querySelectorAll(`[name="${j(H)}"]`))if(z(M)){U=M;break}return G.set(H,U),U};for(let H of $)this.#r(H,q(H)?.value??"");let Y=this.element.getAttribute("data-reactive-compute-reducer-param"),K=Y?JX(Y):null;if(!K){this.#t({},q);return}let W=this.#CX("data-reactive-compute-outputs-param"),B={};for(let[H,U]of Z){let M=q(H);if(U==="string")B[H]=M?.value??"";else{let V=Number(M?.value);B[H]=Number.isFinite(V)?V:0}}let J=K(B,{changed:this.#RX(X,$,Q)})||{},L=[];for(let H of W){if(!(H in J))continue;let U=q(H);if(!U)continue;if(String(J[H])===U.value)continue;U.value=J[H],L.push(U)}for(let H of Object.keys(J)){let U=J[H];if(U===void 0||U===null)continue;this.#r(H,U)}this.#t(J,q);for(let H of L){let U=new Event("input",{bubbles:!0});this.#g.add(U),H.dispatchEvent(U)}}listnavNext(X){this.#i(X,1)}listnavPrev(X){this.#i(X,-1)}listnavPick(X){let Z=this.#P(X),$=Z.findIndex((Q)=>Q.hasAttribute("data-reactive-highlighted"));if($<0)return;X.preventDefault(),Z[$].click()}listnavClose(X){for(let Z of this.#P(X))Z.removeAttribute("data-reactive-highlighted")}#i(X,Z){let $=this.#P(X);if(!$.length)return;X.preventDefault();let Q=$.findIndex((G)=>G.hasAttribute("data-reactive-highlighted")),j=Q<0?Z>0?0:$.length-1:(Q+Z+$.length)%$.length;for(let G of $)G.removeAttribute("data-reactive-highlighted");let z=$[j];z.setAttribute("data-reactive-highlighted","true"),z.scrollIntoView?.({block:"nearest"})}#P(X){let $=(X?.currentTarget??X?.target??this.element).getAttribute?.("data-reactive-listnav-option-param")??this.element.getAttribute("data-reactive-listnav-option-param");if(!$)return[];let Q=this.#X();return Array.from(this.element.querySelectorAll($)).filter((j)=>!j.hidden&&Q(j))}tagsAdd(X){if(!this.#F())return;if(X?.defaultPrevented)return;if(this.#P(X).some((Q)=>Q.hasAttribute?.("data-reactive-highlighted")))return;X?.preventDefault?.();let Z=X?.currentTarget??X?.target;if(!Z)return;if(!this.#YX(String(Z.value??"").split(",")))return;if(Z.value="",this.#R())this.#A()}tagsPick(X){if(!this.#F())return;X?.preventDefault?.();let $=(X?.currentTarget??X?.target)?.getAttribute?.("data-reactive-tag-param");if(!$)return;if(!this.#YX([$]))return;let Q=this.#ZZ();if(!Q)return;Q.value="",this.#A(),Q.focus?.()}tagsRemove(X){if(!this.#F())return;X?.preventDefault?.();let $=(X?.currentTarget??X?.target)?.getAttribute?.("data-reactive-tag-param");if(!$)return;let Q=this.#y();if(!Q)return;let j=this.#v(Q),z=j.filter((G)=>G.toLowerCase()!==$.toLowerCase());if(z.length===j.length)return;this.#KX(Q,z)}nestedAdd(X){X?.preventDefault?.();let Z=X?.currentTarget??X?.target,$=Z?.getAttribute?.("data-reactive-association-param");if(!$)return;if(typeof this.element?.querySelectorAll!=="function")return;let Q=this.#X(),j=[...this.element.querySelectorAll(`[data-reactive-nested-list="${$}"]`)].find(Q),G=[...this.element.querySelectorAll(`[data-reactive-nested-template="${$}"]`)].find(Q)?.content?.firstElementChild;if(!j||!G){this.#GZ($);return}let q=G.cloneNode(!0);this.#zZ(q,this.#jZ()),j.appendChild(q);let Y=Z?.getAttribute?.("data-reactive-nested-from-param"),K=Z?.getAttribute?.("data-reactive-nested-clear-param")==="true",W=this.#MX(q,Y,K);if(Y)W?.focus?.();else[...q.querySelectorAll?.("input, select, textarea")??[]][0]?.focus?.();if(j.getAttribute?.("data-reactive-nested-json")===$)this.#s($)}#MX(X,Z,$){if(!Z)return null;let Q;try{Q=JSON.parse(Z)}catch{return null}if(!Q||typeof Q!=="object")return null;let j=this.#X(),z=[...X.querySelectorAll?.("input, select, textarea")??[]],G=[];for(let[q,Y]of Object.entries(Q)){let K=[...this.element.querySelectorAll?.(Y)??[]].find(j);if(!K)continue;let W=z.find((B)=>this.#a(B.getAttribute?.("name"))===q);if(!W)continue;this.#OX(W,K),G.push(K)}if($)for(let q of G)this.#xX(q);return G[0]??null}#OX(X,Z){if(X.type==="checkbox")X.checked=Z.type==="checkbox"?!!Z.checked:this.#S(Z)!=="";else X.value=this.#S(Z);if(typeof X.dispatchEvent==="function")X.dispatchEvent(new Event("input",{bubbles:!0}))}#xX(X){if(X.type==="checkbox")X.checked=!1;else X.value="";if(typeof X.dispatchEvent==="function")X.dispatchEvent(new Event("input",{bubbles:!0}))}nestedRemove(X){X?.preventDefault?.();let Z=X?.currentTarget??X?.target,$=Z?.closest?.("[data-reactive-nested-row]");if(!$)return;if($.closest?.('[data-controller~="reactive"]')!==this.element)return;let Q=Z?.getAttribute?.("data-reactive-confirm-param"),j=Z?.getAttribute?.("data-reactive-confirm-when-param"),z=this.#w(Q,j);if(!z)return this.#o($);let G=this.#n($),q=this.#PX(z,G);return Promise.resolve().then(()=>R(q,{el:Z,row:$,fields:G})).catch(()=>!1).then((Y)=>{if(Y)this.#o($)})}#PX(X,Z){if(!X.includes("%{"))return X;return X.replace(/%\{(\w+)\}/g,($,Q)=>Object.prototype.hasOwnProperty.call(Z,Q)?Z[Q]:$)}#o(X){let Z=[...X.querySelectorAll?.('input[name$="[_destroy]"]')??[]][0];if(Z){if(Z.value="1",typeof Z.dispatchEvent==="function")Z.dispatchEvent(new Event("input",{bubbles:!0}));X.hidden=!0}else X.parentNode?.removeChild?.(X);this.#N()}syncNestedJson(X){let Z=X?.target;if(!Z||!this.#$(Z))return;this.#N()}#N(){if(typeof this.element?.querySelectorAll!=="function")return;let X=this.#X();for(let Z of[...this.element.querySelectorAll("[data-reactive-nested-json]")].filter(X))this.#s(Z.getAttribute("data-reactive-nested-json"))}#s(X){let Z=this.#X(),$=[...this.element.querySelectorAll(`[data-reactive-nested-list="${X}"]`)].find(Z);if(!$)return;let Q=this.#NX($);if(!Q)return;let j=[];for(let G of[...$.querySelectorAll?.("[data-reactive-nested-row]")??[]]){if(!Z(G)||G.hidden)continue;j.push(this.#n(G))}let z=JSON.stringify(j);if(Q.value===z)return;if(Q.value=z,typeof Q.dispatchEvent==="function")Q.dispatchEvent(new Event("input",{bubbles:!0}))}#NX(X){let Z=X.getAttribute?.("data-reactive-nested-json-field");if(!Z)return null;let $=this.#X();return[...this.element.querySelectorAll(Z)].find($)??null}#n(X){let Z={};for(let $ of[...X.querySelectorAll?.("input, select, textarea")??[]]){let Q=this.#a($.getAttribute?.("name"));if(Q===null||Q==="_destroy")continue;Z[Q]=this.#S($)}return Z}#a(X){if(!X)return null;let Z=X.match(/\[([^\][]+)\]$/);return Z?Z[1]:X}#S(X){if(X.type==="checkbox")return X.checked?X.value||"on":"";return X.value??""}#CX(X){let Z=this.element.getAttribute(X);if(!Z)return[];try{let $=JSON.parse(Z);return Array.isArray($)?$:[]}catch{return[]}}#DX(){let X=this.element.getAttribute("data-reactive-compute-inputs-param");if(!X)return[];try{let Z=JSON.parse(X);if(Array.isArray(Z))return Z.map(($)=>[$,"number"]);if(Z&&typeof Z==="object")return Object.entries(Z);return[]}catch{return[]}}#RX(X,Z,$){let Q=X?.target;if(!Q?.name||typeof Q.closest!=="function")return null;let j=this.#FX(Q.name,$);if(!Z.includes(j))return null;return this.#$(Q)?j:null}#FX(X,Z){if(!Z)return X;let $=`${Z}[`;return X.startsWith($)&&X.endsWith("]")?X.slice($.length,-1):X}#r(X,Z){let $=String(Z);for(let Q of this.#IX(X)){if(Q.textContent===$)continue;Q.textContent=$}}#IX(X){let Z=this.element.querySelectorAll(`[data-reactive-text="${X}"]`);return Array.from(Z).filter(($)=>this.#$($))}#t(X,Z){let $=this.#TX();for(let[Q,j]of Object.entries($)){let z=Q in X?X[Q]:Z(Q)?.value;if(z===void 0||z===null)continue;let G=String(z);for(let q of Array.isArray(j)?j:[j]){if(!oX(q))continue;for(let Y of document.querySelectorAll(q)){if(Y.textContent===G)continue;Y.textContent=G}}}}#TX(){let X=this.element.getAttribute("data-reactive-compute-mirror-param");if(!X)return{};try{let Z=JSON.parse(X);return Z&&typeof Z==="object"&&!Array.isArray(Z)?Z:{}}catch{return{}}}#e(X,Z,$,Q,j,z,G){if(this.#D("reactive:before-dispatch",{action:Z,params:this.#HX($),element:this.element},{cancelable:!0}).defaultPrevented)return;let Y=Number(Q)||0;if(Y>0)return this.#kX(X,Y,Z,$,z,G);let K=Number(j)||0;if(K>0)return this.#bX(X,K,Z,$,z,G);return this.#C(Z,$,z,X,G)}#C(X,Z,$,Q,j){let z=this.#JZ($,Q),G=this.#LZ(X,Q,j),q=this.#XX()?this.#EX($,Q):null;return this.queue=(this.queue??Promise.resolve()).then(()=>this.#mX(X,Z,z,G,q)),this.queue}#EX(X,Z){if(!X?.hide)return null;let $=this.#VX(X,Z);if(!$.length)return null;return()=>{let Q=$.filter((j)=>j.isConnected&&!j.hidden);if(!Q.length)return;console.warn("[phlex-reactive] optimistic: { hide: true } was undone by the reply's re-render — "+"the element is visible again. For an instant delete, return reply.remove so the server removes it; otherwise the hide only flashes.",Q)}}#kX(X,Z,$,Q,j,z){this.#b(X);let G=()=>{this.#b(X),this.#C($,Q,j,X,z)},q=setTimeout(G,Z);X?.addEventListener?.("blur",G,{once:!0}),this.#M.set(X,{timer:q,flush:G})}#b(X){let Z=this.#M.get(X);if(!Z)return;clearTimeout(Z.timer),X?.removeEventListener?.("blur",Z.flush),this.#M.delete(X)}#SX(){for(let X of[...this.#M.keys()])this.#b(X)}#bX(X,Z,$,Q,j,z){let G=this.#H.get(X)??new Map;if(G.has($))return;let q=setTimeout(()=>{if(G.delete($),G.size===0)this.#H.delete(X)},Z);return G.set($,q),this.#H.set(X,G),this.#C($,Q,j,X,z)}#wX(){for(let X of this.#H.values())for(let Z of X.values())clearTimeout(Z);this.#H.clear()}#D(X,Z,{cancelable:$=!1}={}){let Q=new CustomEvent(X,{bubbles:!0,composed:!0,cancelable:$,detail:Z});return(this.element.isConnected?this.element:document).dispatchEvent(Q),Q}#z(X,Z,$,Q){let j=()=>{if(!this.element.isConnected){console.warn("[phlex-reactive] retry() ignored — the reactive root left the DOM");return}return this.#C(X,Z)};this.#D("reactive:error",{action:X,params:$,...Q,retry:j})}#G(X){if(this.element?.isConnected===!1)return;this.element?.setAttribute?.("data-reactive-error",X)}#hX(){this.element?.removeAttribute?.("data-reactive-error")}#yX(){let X=document.querySelector("[data-reactive-error-flash]");if(!X?.content)return;let Z=X.getAttribute("data-reactive-error-flash")||"flash",$=document.getElementById(Z);if(!$)return;$.appendChild(X.content.cloneNode(!0))}#vX(){if(typeof sessionStorage>"u")return Promise.resolve();let X=Number(sessionStorage.getItem(m));if(!Number.isFinite(X)||X<=0)return Promise.resolve();if(!P)P=!0,console.warn(`[phlex-reactive] latency simulator ACTIVE — every action is delayed by ${X}ms. Call PhlexReactive.disableLatencySim() (or clear sessionStorage) to turn it off.`);return new Promise((Z)=>setTimeout(Z,X))}#XX(){return this.element?.getAttribute?.("data-reactive-debug")==="true"}#ZX(){return typeof performance<"u"&&typeof performance.now==="function"?performance.now():Date.now()}#pX(X){if(!X)return[];let Z=[],$=/<turbo-stream\b([^>]*)>/g,Q;while((Q=$.exec(X))!==null){let j=Q[1],z=j.match(/\baction="([^"]*)"/)?.[1]??"?",G=j.match(/\btarget="([^"]*)"/)?.[1];Z.push(G?`${z} → #${G}`:z)}return Z}#fX(X){let{action:Z,status:$,ms:Q}=X,z=`reactive ${this.element?.id?`#${this.element.id} `:""}${Z} → ${$??"—"} (${Math.round(Q)}ms)`;if(console.groupCollapsed(z),console.log(`params: [${X.paramNames.join(", ")}] + collected: [${X.fieldNames.join(", ")}]`),console.log(`encoding: ${X.encoding}`),X.streams.length)console.log(`streams: ${X.streams.join(" ")}`);console.log(`token: ${X.tokenRefreshed?"refreshed ✓":"unchanged"}`),console.groupEnd()}async#mX(X,Z,$,Q,j){let{fields:z,files:G}=this.#jX(),q=this.#HX(Z),Y={...z,...q},K=this.#q,W=G.length>0,B=W?this.#HZ(K,X,Y,G):JSON.stringify({token:K,act:X,params:Y}),J=this.#XX()?{action:X,paramNames:Object.keys(q),fieldNames:Object.keys(z),encoding:W?"multipart":"json",status:null,streams:[],tokenRefreshed:!1,started:this.#ZX()}:null;await this.#vX();try{if(navigator.onLine===!1){this.#Y($),this.#G("offline"),this.#z(X,Z,Y,{kind:"offline"});return}let L;try{let V={Accept:"text/vnd.turbo-stream.html","X-CSRF-Token":this.#PZ()};if(!W)V["Content-Type"]="application/json";let O=this.#NZ();if(O)V["X-Pgbus-Connection"]=O;L=await fetch(this.#OZ(),{method:"POST",headers:V,body:B,credentials:"same-origin",signal:AbortSignal.timeout(this.#xZ())})}catch(V){if(console.error("[phlex-reactive] action error",V),this.#Y($),V?.name==="TimeoutError"||V?.name==="AbortError"){this.#G("timeout"),this.#z(X,Z,Y,{kind:"timeout"});return}this.#yX(),this.#G("network"),this.#z(X,Z,Y,{kind:"network"});return}if(J)J.status=L.status;if(L.redirected){console.error("[phlex-reactive] action was redirected (auth/CSRF?) — no update applied"),this.#Y($),this.#G("redirected"),this.#z(X,Z,Y,{kind:"redirected",status:L.status});return}if(!L.ok){let V=await L.text();if(console.error(`[phlex-reactive] action failed: HTTP ${L.status}`,V),this.#Y($),(L.headers.get("Content-Type")||"").includes("turbo-stream")){let O=this.#QX(V);if(this.#q=O??this.#q,J)this.#$X(J,V,O);window.Turbo.renderStreamMessage(V)}this.#G("http"),this.#z(X,Z,Y,{kind:"http",status:L.status,body:V});return}let H=L.headers.get("Content-Type")||"";if(!H.includes("turbo-stream")){console.error(`[phlex-reactive] expected a turbo-stream, got "${H}" — no update applied`),this.#Y($),this.#G("content-type"),this.#z(X,Z,Y,{kind:"content-type",status:L.status});return}let U=await L.text(),M=this.#QX(U);if(this.#q=M??this.#q,J)this.#$X(J,U,M);if(window.Turbo.renderStreamMessage(U),j)queueMicrotask(j);this.#hX(),this.#D("reactive:applied",{action:X,params:Y,html:U})}catch(L){console.error("[phlex-reactive] action error",L),this.#Y($),this.#D("reactive:error",{action:X,params:Y,kind:"apply"})}finally{if(Q?.(),J)this.#fX({...J,ms:this.#ZX()-J.started})}}#$X(X,Z,$){X.streams=this.#pX(Z),X.tokenRefreshed=$!=null}get#q(){return this.#m??this.tokenValue}set#q(X){this.#m=X}#QX(X){let Z=this.element.id;if(!Z)return X.match(/data-reactive-token-value="([^"]+)"/)?.[1];let{token:$,self:Q}=this.#uX(Z),j=X.match($);if(j)return j[1];let z=X.match(Q);if(z)return z[1].match(/data-reactive-token-value="([^"]+)"/)?.[1];return}#uX(X){let Z=this.#u;if(Z&&Z.id===X)return Z;let $=gX(X);return this.#u={id:X,token:new RegExp(`<turbo-stream\\b[^>]*\\baction="reactive:token"[^>]*\\btarget="${$}"[^>]*\\bdata-reactive-token-value="([^"]+)"`),self:new RegExp(`<turbo-stream\\b[^>]*\\baction="(?:replace|update)"[^>]*\\btarget="${$}"[^>]*>([\\s\\S]*?)</turbo-stream>`)}}#$(X){return X.closest('[data-controller~="reactive"]')===this.element}#X(){if(this.element.querySelectorAll('[data-controller~="reactive"]').length===0)return()=>!0;return(Z)=>this.#$(Z)}#w(X,Z){if(X)return X;if(!Z)return null;let $=Z;if(typeof Z==="string")try{$=JSON.parse(Z)}catch{return console.warn(`[phlex-reactive] malformed conditional confirm payload ${JSON.stringify(Z)} — skipped`),null}if(!$||typeof $!=="object")return null;let{fields:Q}=this.#jX(),j=(G)=>Q[G],z;if(typeof $.predicate==="string"){let G=LX($.predicate);if(!G)return console.warn(`[phlex-reactive] confirm predicate "${$.predicate}" is not registered — proceeding without a dialog (register it with setConfirmPredicate)`),null;z=!!G(Q)}else z=v($.groups?.any,j)===!0;return z?$.message:null}#jX(){let X={},Z=[],$=this.#X();return this.element.querySelectorAll("input[name], select[name], textarea[name]").forEach((Q)=>{if(!$(Q))return;if(Q.type==="file")for(let j of Q.files??[])Z.push({name:Q.name,file:j,multiple:Q.multiple});else if(Q.type==="checkbox")X[Q.name]=Q.checked;else if(Q.type==="radio"){if(Q.checked)X[Q.name]=Q.value}else X[Q.name]=Q.value}),this.element.querySelectorAll("[name]:is(lexxy-editor, trix-editor, [contenteditable=''], [contenteditable=true], [contenteditable=plaintext-only])").forEach((Q)=>{if(!$(Q))return;let j=Q.getAttribute("name");if(!j)return;let z=X[j];if(z==null||z==="")X[j]=Q.value??Q.textContent??Q.innerHTML??""}),{fields:X,files:Z}}#h(){if(typeof this.element?.querySelectorAll!=="function")return;let X=0;if(this.element.querySelectorAll("input[name], select[name], textarea[name]").forEach((Z)=>{if(!this.#$(Z))return;if(Z.type==="file")return;if(this.#gX(Z))Z.setAttribute("data-reactive-dirty","true"),X++;else Z.removeAttribute("data-reactive-dirty")}),X>0)this.element.setAttribute("data-reactive-dirty",String(X));else this.element.removeAttribute("data-reactive-dirty")}#gX(X){if(X.type==="checkbox"||X.type==="radio")return X.checked!==X.defaultChecked;if(X.tag==="select"||X.options)return Array.from(X.options??[]).some((Z)=>Z.selected!==Z.defaultSelected);return X.value!==X.defaultValue}#zX(){let X=this.element.getAttribute?.("data-reactive-dirty"),Z=Number(X);return Number.isFinite(Z)&&Z>0?Z:0}#cX(){if(typeof window>"u"||typeof window.addEventListener!=="function")return;this.#J=(X)=>{if(this.#zX()===0)return;return X.preventDefault(),X.returnValue="You have unsaved changes.",X.returnValue},this.#L=(X)=>{if(this.#zX()===0)return;if(!(typeof window.confirm==="function"?window.confirm("You have unsaved changes. Leave anyway?"):!0))X.preventDefault?.()},window.addEventListener("beforeunload",this.#J),window.addEventListener("turbo:before-visit",this.#L)}#dX(){if(this.#W)this.element.removeEventListener?.("turbo:morph-element",this.#W),this.#W=void 0;if(typeof window<"u"&&typeof window.removeEventListener==="function"){if(this.#J)window.removeEventListener("beforeunload",this.#J);if(this.#L)window.removeEventListener("turbo:before-visit",this.#L)}this.#J=void 0,this.#L=void 0}#lX(){if(this.element.getAttribute?.("data-reactive-show-targets"))return!0;let X=this.element.querySelectorAll?.(i)??[];for(let Z of X)if(this.#$(Z))return!0;return!1}#GX(){if(typeof this.element?.querySelectorAll!=="function")return;let X=this.#X(),Z=this.element.getAttribute?.("data-reactive-scope")||null,$=new Map,Q=(j)=>{if(!$.has(j))$.set(j,this.#nX(j,X,Z));return $.get(j)};for(let j of this.element.querySelectorAll(i)){if(!X(j))continue;let z=j.getAttribute("data-reactive-show");if(z!==null){let K=rX(nX(z),Q);if(K!==null)this.#qX(j,K,X,Z);continue}let G=j.getAttribute("data-reactive-show-field");if(!G)continue;let q=Q(G);if(q===null)continue;let Y=sX(j,q);if(Y===null)continue;this.#qX(j,Y,X,Z)}this.#iX(Q)}#qX(X,Z,$,Q){if(X.hidden=!Z,X.getAttribute("data-reactive-show-disable")!=="true")return;if(typeof X.querySelectorAll!=="function")return;for(let j of X.querySelectorAll("input[name], select[name], textarea[name]"))if($(j))j.disabled=!Z;if(X.name&&$(X))X.disabled=!Z}#iX(X){let Z=this.#sX();for(let[$,Q]of Object.entries(Z)){if($.startsWith("#")){this.#oX($,Q,X);continue}if(!Q||typeof Q!=="object"||Array.isArray(Q))continue;let j=X($);if(j===null)continue;let z=()=>j;for(let[G,q]of Object.entries(Q)){if(!o(G))continue;let Y;if(Array.isArray(q)){if(q.length===0){console.warn(`[phlex-reactive] malformed reactive_show_targets group for ${G} — skipped`);continue}Y=q.every((K)=>u(K,z))}else{let K=KX(q,j);if(K===null){console.warn(`[phlex-reactive] malformed reactive_show_targets predicate for ${G} — skipped`);continue}Y=K}for(let K of document.querySelectorAll(G))K.hidden=!Y}}}#oX(X,Z,$){if(!o(X))return;let Q=Z&&typeof Z==="object"&&!Array.isArray(Z)?Z.any:null,j=aX(Q);if(j===null){console.warn(`[phlex-reactive] malformed reactive_show_targets conditions for ${X} — skipped`);return}if(j.every((G)=>$(G)===null))return;let z=v(Q,$);if(z===null)return;for(let G of document.querySelectorAll(X))G.hidden=!z}#sX(){let X=this.element.getAttribute?.("data-reactive-show-targets");if(!X)return{};try{let Z=JSON.parse(X);if(Z&&typeof Z==="object"&&!Array.isArray(Z))return Z}catch{}return console.warn("[phlex-reactive] malformed data-reactive-show-targets — ignored. "+"Did two reactive_show_targets calls collide on one root? Declare every field in ONE call: reactive_show_targets(mode: { ... }, kind: { ... })"),{}}#nX(X,Z,$){let Q=$&&!X.includes("[")?`${$}[${X}]`:X,j=!1,z=null;for(let G of this.element.querySelectorAll(`[name="${Q}"]`)){if(!Z(G))continue;if(G.type==="checkbox")return G.checked?"true":"false";if(G.type==="radio"){if(G.checked)return G.value??"";j=!0;continue}z??=G}if(z)return z.value??"";return j?"":null}#aX(){if(!this.#Z)return;this.element.removeEventListener?.("input",this.#Z),this.element.removeEventListener?.("change",this.#Z),this.element.removeEventListener?.("turbo:morph-element",this.#Z),this.#Z=void 0}#R(){return!!(this.element.getAttribute?.("data-reactive-filter-input")&&this.element.getAttribute?.("data-reactive-filter-option"))}#rX(){return this.element.getAttribute?.("data-reactive-compute-seed")==="true"}#tX(X){let Z=this.element.getAttribute("data-reactive-filter-input");return!!Z&&typeof X.target?.matches==="function"&&X.target.matches(Z)}#A(){if(typeof this.element?.querySelectorAll!=="function")return;let X=this.element.getAttribute("data-reactive-filter-input"),Z=this.element.getAttribute("data-reactive-filter-option");if(!X||!Z)return;let $=this.#X(),Q=[...this.element.querySelectorAll(X)].find($);if(!Q)return;let j=(Q.value??"").trim().toLowerCase(),z=0;for(let Y of this.element.querySelectorAll(Z)){if(!$(Y))continue;let K=(Y.getAttribute("data-reactive-filter-text")??Y.textContent??"").toLowerCase(),W=Y.hasAttribute?.("data-reactive-tags-selected")||j!==""&&!K.includes(j);if(Y.hidden=W,W)Y.removeAttribute("data-reactive-highlighted");else z++}let G=this.element.getAttribute("data-reactive-filter-group");if(G)for(let Y of this.element.querySelectorAll(G)){if(!$(Y))continue;let K=[...Y.querySelectorAll(Z)].filter($);if(K.length===0)continue;Y.hidden=K.every((W)=>W.hidden)}let q=this.element.getAttribute("data-reactive-filter-empty");if(q){for(let Y of this.element.querySelectorAll(q))if($(Y))Y.hidden=z>0}}#eX(){if(!this.#Q)return;this.element.removeEventListener?.("input",this.#Q),this.element.removeEventListener?.("turbo:morph-element",this.#Q),this.#Q=void 0}#F(){return!!this.element.getAttribute?.("data-reactive-tags-field")}#XZ(){if(typeof this.element?.querySelector!=="function")return!1;return!!this.element.querySelector("[data-reactive-nested-json]")}#y(){if(typeof this.element?.querySelectorAll!=="function")return null;let X=this.element.getAttribute("data-reactive-tags-field");if(!X)return null;let Z=this.#X();return[...this.element.querySelectorAll(X)].find(Z)??null}#v(X){let Z=new Set,$=[];for(let Q of String(X.value??"").split(",")){let j=Q.trim();if(j===""||Z.has(j.toLowerCase()))continue;Z.add(j.toLowerCase()),$.push(j)}return $}#YX(X){let Z=this.#y();if(!Z)return!1;let $=this.#v(Z),Q=new Set($.map((z)=>z.toLowerCase())),j=!1;for(let z of X){let G=String(z??"").trim();if(G===""||Q.has(G.toLowerCase()))continue;Q.add(G.toLowerCase()),$.push(G),j=!0}if(j)this.#KX(Z,$);return j}#KX(X,Z){if(X.value=Z.join(","),typeof X.dispatchEvent==="function")X.dispatchEvent(new Event("input",{bubbles:!0}));this.#p()}#ZZ(){if(typeof this.element?.querySelectorAll!=="function")return null;let X=this.element.getAttribute("data-reactive-filter-input");if(!X)return null;let Z=this.#X();return[...this.element.querySelectorAll(X)].find(Z)??null}#p(){let X=this.#y();if(!X)return;let Z=this.#v(X),$=this.#X();this.#$Z(Z,$),this.#QZ(Z,$)}#$Z(X,Z){let $=[...this.element.querySelectorAll("[data-reactive-tags-list]")].find(Z);if(!$)return;let j=[...this.element.querySelectorAll("[data-reactive-tags-template]")].find(Z)?.content?.firstElementChild;if(!j){if(!this.#c)console.warn("[phlex-reactive] reactive_tags: no chip <template data-reactive-tags-template> found in this root — "+"chips will not render (the hidden field still updates). Add a template with a [data-reactive-tag-text] node and a reactive_tags_remove button."),this.#c=!0;return}while($.firstChild)$.removeChild($.firstChild);for(let z of X){let G=j.cloneNode(!0);G.setAttribute?.("data-reactive-tag",z);let q=G.matches?.("[data-reactive-tag-text]")?G:(G.querySelectorAll?.("[data-reactive-tag-text]")??[])[0];if(q)q.textContent=z;let Y=[...G.querySelectorAll?.('[data-action*="reactive#tagsRemove"]')??[]];if(G.matches?.('[data-action*="reactive#tagsRemove"]'))Y.push(G);for(let K of Y)K.setAttribute?.("data-reactive-tag-param",z);$.appendChild(G)}}#QZ(X,Z){let $=new Set(X.map((Q)=>Q.toLowerCase()));for(let Q of this.element.querySelectorAll("[role=option]")){if(!Z(Q))continue;let j=Q.getAttribute?.("data-reactive-tag-param");if(!j)continue;if($.has(j.toLowerCase()))Q.setAttribute("data-reactive-tags-selected","true"),Q.hidden=!0,Q.removeAttribute?.("data-reactive-highlighted");else if(Q.hasAttribute?.("data-reactive-tags-selected")){if(Q.removeAttribute("data-reactive-tags-selected"),!this.#R())Q.hidden=!1}}if(this.#R())this.#A()}#jZ(){return this.#k=Math.max(this.#k+1,Date.now()),this.#k}#zZ(X,Z){let $=[X,...X.querySelectorAll?.("*")??[]];for(let Q of $)for(let j of["name","id","for"]){let z=Q.getAttribute?.(j);if(z&&z.includes("NEW_ROW"))Q.setAttribute?.(j,z.replaceAll("NEW_ROW",String(Z)))}}#GZ(X){if(this.#d)return;console.warn(`[phlex-reactive] nested rows: no owned [data-reactive-nested-list="${X}"] container + <template data-reactive-nested-template="${X}"> pair found in this root — the add `+"trigger did nothing. Render both inside the same reactive root (reactive_nested_list / reactive_nested_template)."),this.#d=!0}#qZ(){if(!this.#V)return;this.element.removeEventListener?.("turbo:morph-element",this.#V),this.#V=void 0}#YZ(){if(this.#j)this.element.removeEventListener?.("input",this.#j),this.element.removeEventListener?.("change",this.#j),this.#j=void 0;if(this.#B)this.element.removeEventListener?.("turbo:morph-element",this.#B),this.#B=void 0}#KZ(){if(!this.#_)return;this.element.removeEventListener?.("turbo:morph-element",this.#_),this.#_=void 0}#HZ(X,Z,$,Q){let j=new FormData;j.append("token",X),j.append("act",Z);for(let[G,q]of Object.entries($))this.#f(j,`params[${G}]`,q);let z=this.#UZ(Q);for(let{name:G,file:q,multiple:Y}of Q){let W=Y||z.has(G)?`params[${G}][]`:`params[${G}]`;j.append(W,q,q.name)}return j}#f(X,Z,$){if($==null)X.append(Z,"");else if(Array.isArray($))$.forEach((Q,j)=>this.#f(X,`${Z}[${j}]`,Q));else if(typeof $==="object")for(let[Q,j]of Object.entries($))this.#f(X,`${Z}[${Q}]`,j);else X.append(Z,String($))}#UZ(X){let Z=new Map;for(let{name:$}of X)Z.set($,(Z.get($)??0)+1);return new Set([...Z].filter(([,$])=>$>1).map(([$])=>$))}#HX(X){if(!X)return{};try{return typeof X==="string"?JSON.parse(X):X}catch{return{}}}#UX(X){return HX(X)}#WX(X){UX(X,(Z)=>this.#JX(Z))}#JX(X){let Z=X.to;if(Z==="@root")return[this.element];if(typeof Z!=="string"||Z==="")return[];if(X.global)return[...document.querySelectorAll(Z)];return[...this.element.querySelectorAll(Z)].filter(($)=>this.#$($))}#WZ(X,Z){if(X?.checked!=="keep")return!1;let $=Z?.type;return $==="checkbox"||$==="radio"}#JZ(X,Z){if(!X)return null;let $=this.#LX(X,Z,!0);return $.length?$:null}#Y(X){if(!X)return;if(!this.element.isConnected)return;for(let Z of X)Z()}#LZ(X,Z,$){this.#BZ(X,Z);let Q=$?this.#LX($,Z,!1):[];QX();let j=!1;return()=>{if(j)return;j=!0,this.#_Z(X,Z),jX();for(let z of Q)z()}}#LX(X,Z,$){let Q=[];for(let j of this.#VX(X,Z)){if(X.add_class){let z=X.add_class.filter((G)=>!j.classList.contains(G));if(j.classList.add(...z),z.length)Q.push(()=>j.classList.remove(...z))}if(X.remove_class){let z=X.remove_class.filter((G)=>j.classList.contains(G));if(j.classList.remove(...z),z.length)Q.push(()=>j.classList.add(...z))}if(X.toggle_class)X.toggle_class.forEach((z)=>j.classList.toggle(z)),Q.push(()=>X.toggle_class.forEach((z)=>j.classList.toggle(z)));if(X.hide)j.hidden=!0,Q.push(()=>j.hidden=!1);if(X.show)j.hidden=!1,Q.push(()=>j.hidden=!0)}if(Z&&(X.disable||X.text!=null))Q.push(this.#VZ(X,Z));if($&&X.checked==="keep"&&Z&&"checked"in Z){let j=Z.checked;Q.push(()=>Z.checked=!j)}return Q}#VX(X,Z){if(X.to==null)return Z?[Z]:[];return this.#JX({to:X.to})}#VZ(X,Z){let $=this.#O.get(Z);if($)$.count++;else this.#O.set(Z,{count:1,disabled:Z.disabled,html:Z.innerHTML,hadText:X.text!=null,swappedTo:X.text});if(X.disable)Z.disabled=!0;if(X.text!=null)Z.innerHTML=X.text;return()=>this.#AZ(Z,X)}#BZ(X,Z){if(this.#K(Z,X,1),this.#K(this.element,X,1),this.#U.set(X,(this.#U.get(X)??0)+1),this.#T++===0)this.element.setAttribute("aria-busy","true");for(let $ of this.#BX(X))this.#K($,X,1)}#_Z(X,Z){this.#K(Z,X,-1),this.#K(this.element,X,-1);let $=(this.#U.get(X)??1)-1;if($<=0)this.#U.delete(X);else this.#U.set(X,$);if(--this.#T<=0)this.#T=0,this.element.removeAttribute("aria-busy");for(let Q of this.#BX(X))this.#K(Q,X,-1)}#K(X,Z,$){if(!X||typeof X.getAttribute!=="function")return;let Q=this.#E.get(X)??new Map,j=(Q.get(Z)??0)+$;if(j<=0)Q.delete(Z);else Q.set(Z,j);if(Q.size===0){this.#E.delete(X),X.removeAttribute("data-reactive-busy");return}this.#E.set(X,Q),X.setAttribute("data-reactive-busy",[...Q.keys()].join(" "))}#BX(X){return[...this.element.querySelectorAll?.("[data-reactive-busy-on]")??[]].filter(($)=>$.getAttribute("data-reactive-busy-on")===X&&this.#$($))}#AZ(X,Z){let $=this.#O.get(X);if(!$)return;if(--$.count>0)return;if(this.#O.delete(X),!X.isConnected)return;if(Z.disable)X.disabled=$.disabled;if($.hadText&&X.innerHTML===$.swappedTo)X.innerHTML=$.html}#MZ(X){if(!X||typeof X!=="object")return null;let{class:Z,...$}=X;return Z==null?X:{...$,add_class:Z}}#OZ(){return this.#_X??=document.querySelector('meta[name="phlex-reactive-action-path"]')?.content||"/reactive/actions"}#xZ(){if(this.#I!=null)return this.#I;let X=document.querySelector('meta[name="phlex-reactive-timeout"]')?.content,Z=Number(X);return this.#I=Number.isFinite(Z)&&Z>0?Z:30000}#PZ(){return document.querySelector('meta[name="csrf-token"]')?.content??""}#NZ(){return document.querySelector("pgbus-stream-source[connection-id]")?.getAttribute("connection-id")||document.querySelector('meta[name="pgbus-connection-id"]')?.content||null}}export{GZ as resetReactiveDefers,JZ as resetReactiveActivity,VX as registerReactiveVisit,BX as registerReactiveToken,pX as registerReactiveOffline,_X as registerReactiveJs,IX as registerReactiveEffects,DX as registerReactiveDismiss,AX as registerReactiveDefer,d as registerReactiveActions,WZ as reactiveActivityCount,qZ as pendingDeferVia,jX as exitReactiveActivity,gX as escapeRegExp,QX as enterReactiveActivity,fX as enableLatencySim,mX as disableLatencySim,BZ as default,cX as checkReactiveRegistration,LZ as __resetReactiveRegistrationForTest,HZ as __resetReactiveOfflineForTest,UZ as __resetReactiveLatencyForTest,KZ as __resetReactiveEffectsForTest,YZ as __resetReactiveDismissForTest,VZ as __markReactiveConnectedForTest,m as LATENCY_KEY,$X as ACTIVE_ATTR};
2
2
 
3
- //# debugId=730B1A7F3EE4DCD964756E2164756E21
3
+ //# debugId=3D36FCC8A2F9938464756E2164756E21
4
4
  //# sourceMappingURL=reactive_controller.min.js.map