phlex-reactive 0.15.5 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +344 -142
- data/README.md +100 -3
- data/app/controllers/phlex/reactive/actions_controller.rb +10 -0
- data/app/controllers/phlex/reactive/fragments_controller.rb +4 -3
- data/app/javascript/phlex/reactive/core.js +1 -0
- data/app/javascript/phlex/reactive/core.min.js +4 -4
- data/app/javascript/phlex/reactive/core.min.js.map +4 -4
- data/app/javascript/phlex/reactive/features/bindings.js +81 -4
- data/app/javascript/phlex/reactive/features/bindings.min.js +2 -2
- data/app/javascript/phlex/reactive/features/bindings.min.js.map +3 -3
- data/app/javascript/phlex/reactive/features/drag.js +522 -0
- data/app/javascript/phlex/reactive/features/drag.min.js +4 -0
- data/app/javascript/phlex/reactive/features/drag.min.js.map +10 -0
- data/app/javascript/phlex/reactive/reactive_controller.js +2 -0
- data/app/javascript/phlex/reactive/reactive_controller.min.js +4 -4
- data/app/javascript/phlex/reactive/reactive_controller.min.js.map +7 -6
- data/app/javascript/phlex/reactive/runtime.js +57 -23
- data/lib/generators/phlex/reactive/install/templates/phlex_reactive.rb.erb +2 -1
- data/lib/phlex/reactive/apm/subscriber.rb +5 -0
- data/lib/phlex/reactive/apm.rb +5 -6
- data/lib/phlex/reactive/authorization.rb +31 -5
- data/lib/phlex/reactive/client_bindings.rb +19 -0
- data/lib/phlex/reactive/component/css_vars_binding.rb +118 -0
- data/lib/phlex/reactive/component/drag.rb +270 -0
- data/lib/phlex/reactive/component/dsl.rb +15 -1
- data/lib/phlex/reactive/component/helpers.rb +9 -1
- data/lib/phlex/reactive/component/identity.rb +17 -6
- data/lib/phlex/reactive/component/lazy.rb +4 -0
- data/lib/phlex/reactive/component/presence_declaration.rb +4 -0
- data/lib/phlex/reactive/component/registry.rb +76 -6
- data/lib/phlex/reactive/component/sortable.rb +3 -0
- data/lib/phlex/reactive/component.rb +4 -0
- data/lib/phlex/reactive/defer.rb +4 -4
- data/lib/phlex/reactive/doctor/ractor_readiness.rb +65 -0
- data/lib/phlex/reactive/doctor.rb +2 -0
- data/lib/phlex/reactive/engine.rb +31 -4
- data/lib/phlex/reactive/fragment.rb +5 -4
- data/lib/phlex/reactive/held_fields.rb +21 -1
- data/lib/phlex/reactive/param_schema.rb +3 -2
- data/lib/phlex/reactive/pending/markup.rb +27 -9
- data/lib/phlex/reactive/pending.rb +4 -4
- data/lib/phlex/reactive/response.rb +55 -4
- data/lib/phlex/reactive/shareable.rb +87 -0
- data/lib/phlex/reactive/store.rb +73 -0
- data/lib/phlex/reactive/streamable.rb +49 -19
- data/lib/phlex/reactive/test_helpers.rb +6 -0
- data/lib/phlex/reactive/version.rb +1 -1
- data/lib/phlex/reactive.rb +293 -122
- data/sig/phlex/reactive/component.rbs +108 -0
- data/sig/phlex/reactive/param_schema.rbs +23 -0
- data/sig/phlex/reactive/response.rbs +73 -0
- data/sig/phlex/reactive/streamable.rbs +62 -0
- data/sig/phlex/reactive.rbs +231 -0
- metadata +14 -1
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["runtime.js", "core.js"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import { Controller } from \"@hotwired/stimulus\"\n// Import the BARE specifier the engine already pins (phlex/reactive/confirm),\n// NOT a relative \"./confirm.js\" (issue #57). Under importmap-rails + Propshaft\n// the controller is served at its DIGESTED url; a relative sibling import is\n// left untouched (Propshaft rewrites only RAILS_ASSET_URL(...), and the import\n// map resolves ONLY bare specifiers), so \"./confirm.js\" resolves against the\n// digested controller url → an undigested /assets/.../confirm.js that 404s, and\n// the throwing import takes down every Stimulus controller on the page. The\n// bare specifier resolves to the digested asset through the import map, and\n// bundlers/bun resolve it the same way they already resolve\n// \"phlex/reactive/reactive_controller\" (see tsconfig.json paths for the tests).\nimport { confirmResolver } from \"phlex/reactive/confirm\"\n// The feature modules, imported STATICALLY (issue #305). Only the default\n// entry uses these: there `__SPLIT__` is false (scripts/build_client.js\n// defines it), every reference below sits in a `__SPLIT__ ? … : …` branch the\n// minifier keeps, and the features end up in the same file as the runtime,\n// called directly. The opt-in entry (core) is built with `__SPLIT__` true:\n// those branches fold away, and its build resolves these five imports to an\n// empty module, so core.min.js imports no feature until a root needs one.\n//\n// THE ORDER BELOW IS THE DEFAULT BUNDLE'S LAYOUT, not the connect order (that\n// is #connectFeaturesNow's, persist first). The bundler lays the features out\n// in import order, ahead of this file's own code, and gzip only matches text\n// within 32 KB: a feature that repeats what the runtime also says (defer's\n// fetch headers and meta reads, persist's editor selector, form's dirty\n// marker) compresses against it only when it sits close to the runtime. This\n// order was the smallest of a swap search over all of them (issue #310):\n// ~450 B gzipped less than the table order. None of the features runs\n// anything at import time, so the order changes bytes, never behaviour.\nimport * as bindingsFeature from \"phlex/reactive/features/bindings\"\nimport * as computeFeature from \"phlex/reactive/features/compute\"\nimport * as persistFeature from \"phlex/reactive/features/persist\"\nimport * as formFeature from \"phlex/reactive/features/form\"\nimport * as effectsFeature from \"phlex/reactive/features/effects\"\nimport * as devtoolsFeature from \"phlex/reactive/features/devtools\"\nimport * as hintsFeature from \"phlex/reactive/features/hints\"\nimport * as deferFeature from \"phlex/reactive/features/defer\"\nimport * as sortableFeature from \"phlex/reactive/features/sortable\"\nimport * as presenceFeature from \"phlex/reactive/features/presence\"\nimport * as countdownFeature from \"phlex/reactive/features/countdown\"\n\n// phlex/reactive/runtime — the reactive controller WITHOUT its feature\n// modules (issue #275): the code both client entries share. It is not an\n// entry itself and is never pinned; each entry is built with this file inside\n// it. An app reaches it one of two ways, never both:\n//\n// phlex/reactive/reactive_controller the default: this runtime bundled with\n// every feature in one file, built with\n// __SPLIT__ false (issue #305): the\n// features are imported statically and\n// called directly, and connect inside\n// connect(). It has no table and NO way\n// to import one.\n// phlex/reactive/core opt-in: this runtime plus the table of\n// import() calls (core.js). A feature is\n// imported the first time a root on the\n// page needs it (see \"Feature modules\").\n//\n// The ONE generic controller behind every reactive Phlex component. It\n// replaces the per-feature Stimulus controllers you'd otherwise hand-write\n// for interactive components. A component declares its actions in Ruby (via\n// Phlex::Reactive::Component); this controller binds DOM events to a single\n// HTTP round trip and lets Turbo apply the re-rendered component back in\n// (replace by default; method=\"morph\" — Response.morph — preserves focus).\n//\n// Wire format (client -> server), POST <action path>, turbo-stream Accept:\n// { token: \"<signed identity>\", act: \"<action>\", params: {...} } (JSON)\n// (`act`, not `action`: `action` is a reserved Rails routing param.)\n// The token is a MessageVerifier-signed { component, gid } — NO state is sent.\n// When the root holds a chosen <input type=\"file\">, the SAME payload is sent as\n// multipart FormData instead (token/act flat, params bracketed, files appended)\n// so an upload reaches the action (issue #34) — only the encoding differs.\n// The response is a <turbo-stream> that replaces the component by its id.\n//\n// Server -> client live updates use the SAME element id, pushed over the\n// stream transport (pgbus SSE / Action Cable) via the Streamable\n// .broadcast_* methods — so a click and a background broadcast converge on\n// one re-render unit.\n//\n// Custom turbo-stream action: the server tells the actor to full-navigate\n// (e.g. the record's slug changed and the current URL is now dead). It rides a\n// 200 turbo-stream — NOT an HTTP 3xx — so it never trips the response.redirected\n// bail below (which still correctly catches real auth/CSRF redirects). Registered\n// once on the Turbo global (no @hotwired/turbo import — the gem uses window.Turbo\n// everywhere, and a named import is unreliable under importmap/esbuild).\nexport function registerReactiveVisit() {\n const actions = window.Turbo?.StreamActions\n if (!actions || actions[\"reactive:visit\"]) return\n actions[\"reactive:visit\"] = function () {\n const url = this.getAttribute(\"data-url\")\n if (url) window.Turbo.visit(url, { action: \"advance\" })\n }\n}\n\n// Custom turbo-stream action: a TOKEN-ONLY refresh (issue #30). A partial\n// update (Response.streams / reply.streams) re-renders only PART of a component\n// — so there's no full-self replace to carry the next signed token. The server\n// instead emits `<turbo-stream action=\"reactive:token\" target=\"<id>\"\n// data-reactive-token-value=\"<fresh>\">`. #perform's #extractToken already reads\n// the token out of the response body for the NEXT queued request; this handler\n// keeps the DOM in sync too, writing the attribute onto the root element so the\n// `tokenValue` fallback stays fresh. It's a pure attribute set — no node is\n// replaced — so a focused <input> + caret survive (the whole point: update a\n// total cell without tearing down the field the user is typing in).\nexport function registerReactiveToken() {\n const actions = window.Turbo?.StreamActions\n if (!actions || actions[\"reactive:token\"]) return\n actions[\"reactive:token\"] = function () {\n const token = this.getAttribute(\"data-reactive-token-value\")\n const target = this.getAttribute(\"target\")\n if (!token || !target) return\n const el = document.getElementById(target)\n // Stimulus reads the token via the `token` value -> data-reactive-token-value.\n if (el) el.setAttribute(\"data-reactive-token-value\", token)\n }\n}\n\n// Custom turbo-stream action: SERVER-PUSHED client DOM ops (issue #97). The\n// server-side sibling of on_client's runOps — a reply (reply.<verb>.js(ops)) or\n// a broadcast (Streamable.broadcast_js_to) emits\n//\n// <turbo-stream action=\"reactive:js\" target=\"<optional root id>\"\n// data-reactive-ops=\"[[op, args], ...]\"></turbo-stream>\n//\n// and Turbo invokes this handler with `this` bound to that <turbo-stream>\n// element. It runs the ops through the SAME frozen CLIENT_OPS whitelist as\n// runOps (client-side default-deny — an unknown op warns + is skipped), so a\n// forged/stale ops attr can never break the page or execute anything off the\n// vocabulary. NO token, NO fetch — a pure local DOM mutation.\n//\n// `target` (optional, an element id) scopes op resolution to that root: \"@root\"\n// resolves to the target element itself and a selector resolves WITHIN it.\n// Without a target, ops resolve document-wide (a broadcast op like\n// add_class(\"#bell\", ...) that isn't anchored to one component). The op stream\n// is emitted AFTER all render streams in the reply (the endpoint appends it\n// last), so focus(\"[name=next]\") sees the freshly morphed DOM — Turbo applies\n// streams in document order.\nexport function registerReactiveJs() {\n const actions = window.Turbo?.StreamActions\n if (!actions || actions[\"reactive:js\"]) return\n actions[\"reactive:js\"] = function () {\n const list = parseOps(this.getAttribute(\"data-reactive-ops\"))\n if (!list.length) return\n const targetId = this.getAttribute(\"target\")\n // Issue #237: the server stamps the verbose gate on the stream element\n // itself (verbose_errors), so document-scoped ops are diagnosable too.\n const verbose = this.getAttribute(\"data-reactive-verbose\") === \"true\"\n // With a target: scope to that element (missing → no-op). Without: document.\n const root = targetId ? document.getElementById(targetId) : null\n if (targetId && !root) {\n if (verbose) {\n if (__SPLIT__) withFeature(\"devtools\", (devtools) => devtools.missingRoot(targetId))\n else devtoolsFeature.missingRoot(targetId)\n }\n return\n }\n applyOps(\n list,\n (args) => streamOpTargets(args, root),\n verbose\n ? (name, args) =>\n __SPLIT__\n ? withFeature(\"devtools\", (devtools) => devtools.diagnoseStream(name, args, root))\n : devtoolsFeature.diagnoseStream(name, args, root)\n : undefined,\n )\n }\n}\n\n// --- Deferred and lazy renders (issues #165, #276, #277) ---------------------\n// reply.defer, reactive_lazy and reactive_lazy(on:/cache:) live in the defer\n// feature module (issue #275, features/defer.js). The core keeps only the\n// `reactive:defer` stream action's REGISTRATION: such a stream can reach a\n// page that has not loaded the module yet (the first reply.defer on a page\n// with no lazy root), and it must not be lost. The <turbo-stream> element is\n// handed over once the module is here — at once when it already is.\nexport function registerReactiveDefer() {\n const actions = window.Turbo?.StreamActions\n if (!actions || actions[\"reactive:defer\"]) return\n actions[\"reactive:defer\"] = function () {\n if (__SPLIT__) withFeature(\"defer\", (defer) => defer.streamAction(this))\n else deferFeature.streamAction(this)\n }\n}\n\n// --- Around a stream render: effects and dismissing flashes (issues #100, #215) ---\n// Both live in the effects feature module (issue #275, features/effects.js).\n// The runtime keeps the ONE document-level turbo:before-stream-render listener\n// (it fires for every <turbo-stream>: a reply AND a broadcast) and hands each\n// event to the module, which wraps event.detail.render.\n//\n// In the default client the module is always here. The opt-in client may not\n// have imported it yet when a stream first needs it; what happens then is that\n// entry's business (core.js installs it: the stream's render waits for the\n// import). The runtime only offers the event.\nlet streamRenderRegistered = false\nlet streamWithoutEffects = null\n\nexport function registerReactiveStreamRender() {\n if (streamRenderRegistered) return\n if (typeof document === \"undefined\" || typeof document.addEventListener !== \"function\") return\n streamRenderRegistered = true\n document.addEventListener(\"turbo:before-stream-render\", (event) => {\n streamOwnReply(event)\n streamIntoRoots(event)\n streamModals(event)\n // The opt-in entry sees every stream (it may hold one for a module the\n // incoming content needs); with no entry hook, a loaded effects module\n // wraps the stream directly — in the default entry, always.\n if (!__SPLIT__) effectsFeature.wrap(event)\n else if (streamWithoutEffects) streamWithoutEffects(event, featureModules.get(\"effects\"))\n else featureModules.get(\"effects\")?.wrap(event)\n })\n}\n// The names this registration had while it was two listeners.\nexport { registerReactiveStreamRender as registerReactiveDismiss, registerReactiveStreamRender as registerReactiveEffects }\n\n// --- A stream into a connected root (issue #356) ---\n// A morph of a root makes it re-read its feature markers (#boundRootMorph); a\n// stream that renders INTO it — append, prepend or update of the root or a\n// child, before, after or replace of a child — fires neither a lifecycle nor\n// a morph of the root, so a marker it brings was never connected. The roots\n// a stream renders into are resolved here, before anything (effects, the opt-in\n// entry's hold) wraps Turbo's render; once that render has run, each one still\n// connected is told (STREAMED, not bubbling: an outer root's content is not\n// its own). The root re-runs its connected features' seeds and connects what\n// the new content needs, in both entries.\nconst STREAMED = \"reactive:streamed\"\n\nfunction streamIntoRoots(event) {\n const detail = event.detail\n const render = detail?.render\n const stream = detail?.newStream ?? event.target\n const action = stream?.getAttribute?.(\"action\")\n const into = /^(append|prepend|update)$/.test(action)\n if (typeof render !== \"function\" || !(into || /^(before|after|replace)$/.test(action))) return\n const id = stream.getAttribute(\"target\")\n const selector = stream.getAttribute(\"targets\")\n const roots = new Set()\n for (const target of id ? [document.getElementById(id)] : selector ? document.querySelectorAll(selector) : []) {\n const root = (into ? target : target?.parentElement)?.closest?.('[data-controller~=\"reactive\"]')\n if (root) roots.add(root)\n }\n if (roots.size === 0) return\n detail.render = async (el) => {\n await render(el)\n for (const root of roots) if (root.isConnected) root.dispatchEvent(new Event(STREAMED))\n }\n}\n\n// --- Modal dialogs (issue #365) ---\n// show_modal / close_dialog reach the browser's own modal dialog: the top\n// layer, the focus trap, ::backdrop, Escape and focus restored to the opener.\n// A dialog the SERVER renders open with data-reactive-modal becomes modal as\n// soon as the client sees it: at a root's connect (a full page, a replaced\n// root), after a morph of a root or of the page, and after a stream whose\n// content holds a dialog. Duck-typed on tagName (the submitFormFor precedent:\n// fake-node and test friendly).\nfunction isDialog(el) {\n return el?.tagName === \"DIALOG\" && typeof el.showModal === \"function\"\n}\n\n// `:modal` is the only way to tell a modal dialog from one opened with the\n// `open` attribute. An engine that does not know the pseudo-class throws a\n// SyntaxError; it then counts as not modal (show_modal reopens it modally).\nfunction isModal(dialog) {\n try {\n return dialog.matches(\":modal\")\n } catch {\n return false\n }\n}\n\nfunction showModal(el) {\n if (!isDialog(el)) {\n // Said where a zero-target op is (issue #237): under verbose_errors or debug.\n if (el?.closest?.('[data-reactive-verbose=\"true\"], [data-reactive-debug=\"true\"]')) {\n console.warn(`[phlex-reactive] show_modal targets a <dialog>, not <${el.localName}> — skipped`)\n }\n return\n }\n if (!el.isConnected || isModal(el)) return\n // showModal() throws on a dialog already open non-modally: close it first,\n // in the same task, so nothing paints in between.\n if (el.open) el.close()\n el.showModal()\n}\n\nfunction closeDialog(el) {\n if (isDialog(el) && el.open) el.close()\n}\n\n// Make every open dialog[data-reactive-modal] in `scope` modal — `scope` itself\n// too, for a root that is the dialog.\nfunction upgradeModals(scope) {\n const sel = \"dialog[data-reactive-modal]\"\n const dialogs = [...(scope?.querySelectorAll?.(sel) ?? [])]\n if (scope?.matches?.(sel)) dialogs.push(scope)\n for (const dialog of dialogs) {\n if (dialog.open && !isModal(dialog)) showModal(dialog)\n }\n}\n\n// After a stream whose content holds a dialog has rendered, upgrade the page's\n// marked dialogs. Document-wide: a replace's target is gone by then.\nfunction streamModals(event) {\n const detail = event.detail\n const render = detail?.render\n const stream = detail?.newStream ?? event.target\n if (typeof render !== \"function\" || !stream?.querySelector?.(\"template\")?.content?.querySelector?.(\"dialog\")) return\n detail.render = async (el) => {\n await render(el)\n upgradeModals(document)\n }\n}\n\nlet modalsRegistered = false\n\nexport function registerReactiveModals() {\n if (modalsRegistered) return\n if (typeof document === \"undefined\" || typeof document.addEventListener !== \"function\") return\n modalsRegistered = true\n // A morph of a root (a reply or broadcast morph, a page refresh) may have\n // inserted an open marked dialog or opened one; a page morph covers dialogs\n // outside any root.\n document.addEventListener(\"turbo:morph-element\", (event) => {\n if (event.target?.matches?.('[data-controller~=\"reactive\"]')) upgradeModals(event.target)\n })\n document.addEventListener(\"turbo:morph\", () => upgradeModals(document))\n // A morph that removes `open` from a modal dialog (the server now renders it\n // closed): close() it instead, which leaves the top layer, ends the page's\n // inertness and restores focus. Removing the attribute alone left all that\n // in place under the older spec text, and close() is a no-op once `open` is\n // gone — so the removal is cancelled and close() removes it. Idiomorph asks\n // \"remove\" for EVERY attribute of the old element, kept or not, and that ask\n // carries no new element: whether `open` is really leaving is read from the\n // element morph's new render, which comes first.\n const closing = new WeakSet()\n document.addEventListener(\"turbo:before-morph-element\", (event) => {\n const el = event.target\n const next = event.detail?.newElement\n if (!isDialog(el) || !next) return\n if (next.hasAttribute?.(\"open\")) closing.delete(el)\n else closing.add(el)\n })\n document.addEventListener(\"turbo:before-morph-attribute\", (event) => {\n const el = event.target\n const detail = event.detail\n if (detail?.attributeName !== \"open\" || detail.mutationType !== \"remove\") return\n if (!closing.has(el) || !el.open || !isModal(el)) return\n closing.delete(el)\n event.preventDefault()\n el.close()\n })\n}\n\n\n// --- keep: a morph re-applies what a kept client op did (issue #369) ---------\n// A morph rewrites `class`, `hidden` and every attribute from the server\n// render, so a selection made with on_client ops was lost to the next reply or\n// broadcast morph. An op with keep: records its RESULT on each target — each\n// class on or off, `hidden`, an attribute's value (null: absent) — and the one\n// document listener below puts that back after Turbo morphs the element in\n// place. The result, not the op: a toggle must not flip again on every morph.\n// The latest kept op on an element and a class or attribute wins, so \"clear\n// all, then select one\" leaves one. Classes and attributes no kept op touched\n// follow the render. Weak: an element a stream replaces is a new element.\n//\n// One Map per element: \".name\" → whether that class is on; an attribute name →\n// its value, null when absent. show/hide/toggle keep the `hidden` attribute,\n// so the morph puts back the visibility without running a transition again.\n// (No attribute name starts with \".\", so the two kinds of key never meet.)\n// An entry is re-inserted on every kept op, so the Map replays in op order:\n// a kept add_class after a kept set_attr(\"class\", …) still wins.\nconst kept = new WeakMap()\n\nfunction keepResult(el, name, args, resolveTargets) {\n const state = kept.get(el) ?? new Map()\n kept.set(el, state)\n const put = (key, value) => {\n state.delete(key)\n state.set(key, value)\n }\n if (name.endsWith(\"_class\")) for (const c of args.classes ?? []) put(`.${c}`, el.classList.contains(c))\n else if (name.endsWith(\"_attr\")) attrRefused(args.name) || put(args.name, el.getAttribute(args.name))\n else if (name === \"show\" || name === \"hide\" || name === \"toggle\") {\n put(\"hidden\", el.getAttribute(\"hidden\"))\n // The expanded: trigger's aria-expanded is kept on the trigger: a document\n // listener has no op scope to resolve it from later.\n if (args.expanded != null) {\n for (const trigger of resolveTargets({ ...args, to: args.expanded })) {\n keepResult(trigger, \"set_attr\", { name: \"aria-expanded\" })\n }\n }\n }\n}\n\nlet keepRegistered = false\n\nexport function registerReactiveKeep() {\n if (keepRegistered) return\n if (typeof document === \"undefined\" || typeof document.addEventListener !== \"function\") return\n keepRegistered = true\n // turbo:morph-element fires for every element Idiomorph morphed in place,\n // after its attributes were synced from the new render.\n document.addEventListener(\"turbo:morph-element\", (event) => {\n const el = event.target\n for (const [key, value] of kept.get(el) ?? []) {\n if (key[0] === \".\") el.classList.toggle(key.slice(1), value)\n else if (value == null) el.removeAttribute(key)\n else if (el.getAttribute(key) !== value) el.setAttribute(key, value)\n }\n })\n}\n\n\n// --- An image fallback that recovers (issue #383) ---------------------------\n// data-reactive-image-fallback='{\"hide\":true,\"alt\":\"#name\"}' on an <img>. On\n// `error` it gets data-reactive-image-state=\"error\" (plus `hidden` with hide,\n// and its alt shown in the `alt` node of its own reactive root); on `load`,\n// \"loaded\". A later src clears the error: copy_media and preview_file reset\n// it as they write (resetImage), any src that loads clears it on `load`, and\n// a morph that changes the src lets the browser try again. A morph that keeps\n// the src puts the state back. An error with no src is not an error (a\n// removed or emptied src). One document listener, capture phase (neither\n// event bubbles); nothing per root at connect.\nconst IMAGE_FALLBACK = \"data-reactive-image-fallback\"\nconst IMAGE_STATE = \"data-reactive-image-state\"\n// Marks the `hidden` the fallback set, so a reset removes only that one. On\n// the element, not in a WeakSet: a Turbo snapshot restores a NEW node.\nconst IMAGE_HIDDEN = \"data-reactive-image-hidden\"\nlet imageFallbackListeners = null\n\n// --- A wrapper an op hides (issues #391, #392) -------------------------------\n// preview_file's wrap: and reactive_image_fallback's wrap: hide the box around\n// an img. The mark says the runtime set that `hidden`, so a show removes only\n// a hide of its own, never one the author wrote. On the element, for the\n// IMAGE_HIDDEN reason. Visibility only: a wrapper usually holds the img, so\n// nothing here writes its text.\nconst WRAP_HIDDEN = \"data-reactive-wrap-hidden\"\n\nfunction hideWrap(el) {\n if (el.hasAttribute(\"hidden\")) return\n el.setAttribute(\"hidden\", \"\")\n el.setAttribute(WRAP_HIDDEN, \"\")\n}\n\nfunction showWrap(el) {\n if (!el.hasAttribute(WRAP_HIDDEN)) return\n el.removeAttribute(\"hidden\")\n el.removeAttribute(WRAP_HIDDEN)\n}\n\nfunction imageFallback(img) {\n if (img?.localName !== \"img\") return null\n const raw = img.getAttribute(IMAGE_FALLBACK)\n if (raw == null) return null\n try {\n return JSON.parse(raw) || {}\n } catch {\n return {}\n }\n}\n\nconst hasImageSrc = (img) => img.getAttribute(\"src\") || img.getAttribute(\"srcset\")\n\n// The node that shows the alt text: a selector resolved inside the img's own\n// root only (a document listener has no op scope).\nfunction imageRootNode(img, selector) {\n if (typeof selector !== \"string\" || !selector) return null\n try {\n return img.closest('[data-controller~=\"reactive\"]')?.querySelector(selector) ?? null\n } catch {\n return null\n }\n}\n\nfunction imageAltNode(img, wire) {\n const node = imageRootNode(img, wire.alt)\n // Never a node that holds the img: its textContent would wipe the img.\n return node && !node.contains(img) ? node : null\n}\n\nfunction setImageState(img, wire, value) {\n if (value === \"error\") {\n if (wire.hide && !img.hasAttribute(\"hidden\")) {\n img.setAttribute(\"hidden\", \"\")\n img.setAttribute(IMAGE_HIDDEN, \"\")\n }\n // wrap: (issue #392) may hold the img: only its visibility changes.\n if (wire.wrap) {\n const wrap = imageRootNode(img, wire.wrap)\n if (wrap) hideWrap(wrap)\n else console.warn(`[phlex-reactive] reactive_image_fallback wrap: ${JSON.stringify(wire.wrap)} is not in the root — skipped`)\n }\n const node = imageAltNode(img, wire)\n if (node) {\n node.textContent = img.getAttribute(\"alt\") ?? \"\"\n node.hidden = false\n }\n } else resetImage(img, wire)\n img.setAttribute(IMAGE_STATE, value)\n}\n\n// Undo what an error did — only what it did: `hidden` stays when the author\n// set it. Called by copy_media and preview_file when they change the src.\nfunction resetImage(img, wire = imageFallback(img)) {\n if (!wire || img.getAttribute(IMAGE_STATE) == null) return\n if (img.getAttribute(IMAGE_STATE) === \"error\") {\n if (img.hasAttribute(IMAGE_HIDDEN)) img.removeAttribute(\"hidden\")\n const wrap = imageRootNode(img, wire.wrap)\n if (wrap) showWrap(wrap)\n const node = imageAltNode(img, wire)\n if (node) node.hidden = true\n }\n img.removeAttribute(IMAGE_STATE)\n img.removeAttribute(IMAGE_HIDDEN)\n}\n\nfunction onImageEvent(event) {\n const img = event.target\n const wire = imageFallback(img)\n if (!wire) return\n if (event.type === \"load\") setImageState(img, wire, \"loaded\")\n else if (hasImageSrc(img)) setImageState(img, wire, \"error\")\n else resetImage(img, wire)\n}\n\nexport function registerReactiveImageFallback() {\n if (imageFallbackListeners) return\n if (typeof document === \"undefined\" || typeof document.addEventListener !== \"function\") return\n // A morph that keeps the src keeps the state: the render carries none, and\n // the browser loads nothing new to tell us again. Put it back once the\n // whole morph is done, so a later-morphed alt node is shown again too.\n const kept = new WeakMap()\n imageFallbackListeners = {\n error: onImageEvent,\n load: onImageEvent,\n \"turbo:before-morph-element\": (event) => {\n const img = event.target\n const next = event.detail?.newElement\n const value = img?.getAttribute?.(IMAGE_STATE)\n if (value == null || !next || !imageFallback(img)) return\n const same = (name) => img.getAttribute(name) === next.getAttribute?.(name)\n if (same(\"src\") && same(\"srcset\")) kept.set(img, value)\n },\n \"turbo:morph-element\": (event) => {\n const img = event.target\n const value = kept.get(img)\n if (value === undefined) return\n kept.delete(img)\n queueMicrotask(() => {\n const wire = imageFallback(img)\n if (wire) setImageState(img, wire, value)\n })\n },\n }\n for (const [name, fn] of Object.entries(imageFallbackListeners)) document.addEventListener(name, fn, true)\n // An image that finished before this ran (a cached 404) fired its event\n // unheard: read it off the element once. naturalWidth 0 is also a loaded\n // SVG with only a viewBox, so decode() decides (it rejects a broken image).\n for (const img of document.querySelectorAll?.(`img[${IMAGE_FALLBACK}]`) ?? []) {\n if (!img.complete || !hasImageSrc(img)) continue\n // A decode() that settles after the src changed decoded the OLD one: ignore it.\n const was = `${img.getAttribute(\"src\")} ${img.getAttribute(\"srcset\")}`\n const mark = (value) => `${img.getAttribute(\"src\")} ${img.getAttribute(\"srcset\")}` === was && setImageState(img, imageFallback(img) ?? {}, value)\n if (img.naturalWidth > 0) mark(\"loaded\")\n else if (typeof img.decode === \"function\") img.decode().then(() => mark(\"loaded\"), () => mark(\"error\"))\n else mark(\"error\")\n }\n}\n\n\n// For the opt-in entry: what to do with a stream before its render — hold it\n// for a module the incoming content needs. `handle(event, effects)` gets the\n// loaded effects module (or undefined) and wraps the stream with it itself.\nexport function onReactiveStreamWithoutEffects(handle) {\n streamWithoutEffects = handle\n}\n\n\n// --- A morph keeps the focused field's value (issue #338) ---\n// Turbo's stream morph (reply.morph, a broadcast morph) runs Idiomorph without\n// ignoreActiveValue, so a field being typed in took the render's value: a\n// normalised \"Hello\" over \"Hello \", or the value as the request left. Two\n// document listeners give a focused input/textarea inside a reactive root\n// Idiomorph's own ignoreActiveValue behaviour, minus the stale default:\n// before-morph-element — write the field's DEFAULT (its value attribute)\n// from the new render ourselves; with the user's\n// edit on it (the dirty-value flag) that never\n// touches .value, so dirty tracking still re-scans\n// against the saved value. A textarea's default is\n// its text child, which Idiomorph still morphs.\n// before-morph-attribute — cancel `value` on that field: Idiomorph honours it\n// for the attribute AND the property. Nothing else\n// (a root's token, a field's class) is held.\n// data-reactive-morph-value on a field lets the morph write it while focused.\n// --- An engine without contenteditable=\"plaintext-only\" (issue #355) ---\n// reactive_editable renders contenteditable=\"plaintext-only\": the browser\n// itself then pastes plain text and inserts \"\\n\" for Enter. An engine without\n// it reads the value as INVALID — the element is not editable, cannot even be\n// focused — so there every reactive editable is switched to \"true\" (and the\n// bindings feature does the plain-text paste itself). However it reaches the\n// page: present at load, appended by a stream into a root that is already\n// connected, loaded in a frame, written back by a morph of an unfocused\n// editable, or made reactive by a morph. One observer, only in such an engine; elsewhere the probe is all\n// that runs. Detected once per page.\nlet plaintextOnly\nlet plaintextObserver = null\nconst PLAINTEXT_EDITABLE = \"[data-reactive-editable][contenteditable=plaintext-only]\"\n\nfunction plaintextSupported() {\n if (plaintextOnly === undefined) {\n try {\n const probe = document.createElement(\"div\")\n probe.contentEditable = \"plaintext-only\"\n plaintextOnly = probe.contentEditable === \"plaintext-only\"\n } catch {\n plaintextOnly = false\n }\n }\n return plaintextOnly\n}\n\nfunction makeEditable(el) {\n if (el.matches?.(PLAINTEXT_EDITABLE)) el.setAttribute(\"contenteditable\", \"true\")\n el.querySelectorAll?.(PLAINTEXT_EDITABLE).forEach(makeEditable)\n}\n\nexport function registerReactivePlaintext() {\n // (No element factory, no page to observe: never probe a stub document.)\n if (plaintextObserver || typeof document === \"undefined\" || typeof document.createElement !== \"function\") return\n if (plaintextSupported()) return\n makeEditable(document)\n plaintextObserver = new MutationObserver((records) => {\n for (const record of records) {\n if (record.type === \"attributes\") makeEditable(record.target)\n else record.addedNodes.forEach(makeEditable)\n }\n })\n plaintextObserver.observe(document.documentElement, {\n childList: true,\n subtree: true,\n attributes: true,\n // (a morph can also make an existing plaintext-only element reactive)\n attributeFilter: [\"contenteditable\", \"data-reactive-editable\"],\n })\n}\n\n// For tests: what the probe answers (undefined probes again), with no\n// observer installed.\n\nlet morphFocusRegistered = false\n\n//\n// A reactive_editable (issue #351) is held the same way, but its \"value\" is its\n// CHILDREN: a focused one keeps them (text and caret) while every attribute\n// still morphs — copied here from the render, then the element's morph is\n// cancelled (cancelling is all-or-nothing in Idiomorph). Its contenteditable\n// stays as it is: the runtime switches it to \"true\" in an engine without\n// plaintext-only (registerReactivePlaintext, #355). Every morph of an editable, held or not, records the\n// render's text as its SERVER TEXT (the Escape revert, the dirty baseline).\nfunction holdsTypedValue(el) {\n return (\n el === document.activeElement &&\n (el.localName === \"input\" || el.localName === \"textarea\" || el.hasAttribute(\"data-reactive-editable\")) &&\n !el.hasAttribute(\"data-reactive-morph-value\") &&\n !!el.closest('[data-controller~=\"reactive\"]')\n )\n}\n\nexport function registerReactiveMorphFocus() {\n if (morphFocusRegistered) return\n if (typeof document === \"undefined\" || typeof document.addEventListener !== \"function\") return\n morphFocusRegistered = true\n document.addEventListener(\"turbo:before-morph-element\", (event) => {\n const el = event.target\n const next = event.detail?.newElement // absent when Turbo asks before a removal\n if (!next || event.defaultPrevented) return\n const holds = holdsTypedValue(el)\n // Before the default below is overwritten (issue #370).\n if (holds) noteHeld(el, next)\n if (el.hasAttribute?.(\"data-reactive-editable\")) {\n serverTexts.set(el, editableValue(next))\n if (!holds) return\n for (const { name } of [...el.attributes]) {\n if (!next.hasAttribute(name) && name !== \"contenteditable\" && name !== HELD) el.removeAttribute(name)\n }\n for (const { name, value } of next.attributes) {\n if (name !== \"contenteditable\" && el.getAttribute(name) !== value) el.setAttribute(name, value)\n }\n return event.preventDefault()\n }\n if (el.localName !== \"input\" || !holds) return\n const value = next.getAttribute(\"value\")\n if (value == null) el.removeAttribute(\"value\")\n else if (el.getAttribute(\"value\") !== value) el.setAttribute(\"value\", value)\n })\n // An editable a morph never reached (a stream append) records its server\n // text when first focused — before anything can be typed into it.\n document.addEventListener(\"focusin\", (event) => seedServerText(event.target))\n document.addEventListener(\"turbo:before-morph-attribute\", (event) => {\n const name = event.detail?.attributeName\n if ((name === \"value\" || name === HELD) && holdsTypedValue(event.target)) event.preventDefault()\n })\n // The user took theirs: the field reads its default again.\n document.addEventListener(\"input\", (event) => {\n const el = event.target\n if (el?.hasAttribute?.(HELD) && typedValue(el) === serverValue(el)) el.removeAttribute(HELD)\n })\n}\n\n// --- Knowing what was held (issue #370) ---\n// A morph that holds a focused field (above) over a render whose value differs\n// from the field's default — its last server value — means someone else changed\n// the field while the user typed. The field is marked data-reactive-held (kept\n// through later morphs while focused, like `value`), a bubbling reactive:held\n// { name, theirs } is dispatched, and the root's next action sends the names as\n// X-Reactive-Held. The root's own reply never marks (it answers exactly those\n// values), and once applied the root holds nothing. A morph of the field while\n// unfocused writes the server's value and drops the mark with it.\nconst HELD = \"data-reactive-held\"\nconst ROOT = '[data-controller~=\"reactive\"]'\nlet replyRoot = null\nconst replying = new Map()\n\nfunction typedValue(el) {\n return el.hasAttribute(\"data-reactive-editable\") ? editableValue(el) : el.value\n}\n\nfunction serverValue(el) {\n return el.hasAttribute(\"data-reactive-editable\") ? serverText(el) : el.defaultValue\n}\n\nfunction renderedValue(next) {\n if (next.hasAttribute(\"data-reactive-editable\")) return editableValue(next)\n return next.localName === \"textarea\" ? next.textContent : (next.getAttribute(\"value\") ?? \"\")\n}\n\nfunction noteHeld(el, next) {\n const name = el.getAttribute(\"name\")\n // A checkbox's or radio's value is the option, not what the user typed.\n if (!name || /^(checkbox|radio)$/.test(el.type) || replying.has(el.closest(ROOT))) return\n const theirs = renderedValue(next)\n if (theirs === typedValue(el)) return el.removeAttribute(HELD)\n if (theirs === serverValue(el)) return\n el.setAttribute(HELD, \"\")\n el.dispatchEvent(new CustomEvent(\"reactive:held\", { bubbles: true, detail: { name, theirs } }))\n}\n\n// The names a root's next request reports: its own marked fields (not a nested\n// root's), still different from their default. As a header value: JSON with\n// every non-ASCII character escaped (fetch refuses a code point above 0xFF).\nfunction heldHeader(root) {\n const names = []\n for (const el of root.querySelectorAll(`[${HELD}]`)) {\n if (el.closest(ROOT) !== root) continue\n const name = el.getAttribute(\"name\")\n if (typedValue(el) === serverValue(el)) el.removeAttribute(HELD)\n else if (name && !names.includes(name)) names.push(name)\n }\n if (names.length === 0) return null\n return JSON.stringify(names).replace(/[\\u007f-]/g, (c) => `\\\\u${c.charCodeAt(0).toString(16).padStart(4, \"0\")}`)\n}\n\nfunction releaseHeld(root) {\n for (const el of root.querySelectorAll(`[${HELD}]`)) if (el.closest(ROOT) === root) el.removeAttribute(HELD)\n}\n\n// Hand a root's own reply to Turbo: while its streams render nothing in the\n// root is marked, and once they have the root holds nothing. Turbo announces\n// each stream synchronously inside renderStreamMessage (streamOwnReply wraps\n// its render, innermost); a reply none of whose streams rendered releases now.\n// An error reply (release false) never marks either, but releases nothing: the\n// server refused those values rather than answering them.\nfunction renderOwnReply(root, html, release = true) {\n const own = { root, streams: 0, release }\n const previous = replyRoot\n replyRoot = own\n try {\n window.Turbo.renderStreamMessage(html)\n } finally {\n replyRoot = previous\n }\n if (release && own.streams === 0) releaseHeld(root)\n}\n\nfunction streamOwnReply(event) {\n const own = replyRoot\n const detail = event.detail\n const render = detail?.render\n if (!own || typeof render !== \"function\") return\n own.streams++\n detail.render = async (el) => {\n replying.set(own.root, (replying.get(own.root) || 0) + 1)\n try {\n return await render(el)\n } finally {\n const left = replying.get(own.root) - 1\n if (left) replying.set(own.root, left)\n else replying.delete(own.root)\n if (own.release) releaseHeld(own.root)\n }\n }\n}\n\n\n// The framework-owned act a reactive_lazy(on:) shell sends (issue #276).\n// Lockstep with Phlex::Reactive::Component::Lazy::MATERIALIZE_ACTION.\nconst LAZY_MATERIALIZE_ACTION = \"__materialize\"\n\n// Offline CSS hook (issue #101). Mirror data-reactive-offline on\n// document.documentElement from navigator.onLine, kept in sync by the window\n// online/offline events — so an app can dim a save button or show a banner with\n// PURE CSS and zero JS ([data-reactive-offline] .save { pointer-events: none }).\n// Guarded on window (needed for addEventListener AND navigator) so importing the\n// module in a non-browser (bun test) context is a no-op, and registered once\n// (the online/offline listeners are NOT {once}, so a second registerReactiveActions\n// call must not stack duplicates) — mirroring the dismiss guard + reset seam.\nlet offlineRegistered = false\nexport function registerReactiveOffline() {\n if (offlineRegistered) return\n if (typeof window === \"undefined\" || typeof document === \"undefined\") return\n if (typeof window.addEventListener !== \"function\") return\n offlineRegistered = true\n // toggleAttribute(name, force) writes data-reactive-offline=\"\" (a bare boolean\n // attr the [data-reactive-offline] selector matches) or removes it — never the\n // \"true\" string. navigator.onLine === false is the reliable direction (a false\n // \"online\" is spec-permitted but rare, and this is only a presentational hook —\n // the authoritative offline signal is the #perform gate, not this attribute).\n // Fully defensive: a missing documentElement/toggleAttribute/navigator degrades\n // to a no-op — a presentational hook must NEVER throw during bootstrap.\n const sync = () => {\n const root = document.documentElement\n if (typeof root?.toggleAttribute !== \"function\") return\n root.toggleAttribute(\"data-reactive-offline\", globalThis.navigator?.onLine === false)\n }\n sync() // seed synchronously so first paint is correct\n window.addEventListener(\"online\", sync)\n window.addEventListener(\"offline\", sync)\n}\n\n\n// Latency simulator dev aid (issue #102): features/devtools.js (issue #275). The\n// core keeps the two places that decide whether the module is needed at all:\n// the page opted into the console handle (the phlex-reactive-env meta), or a\n// delay is stored for this tab. Read live per request, like the CSRF token.\nconst LATENCY_KEY = \"phlex-reactive:latency\"\n\nfunction latencyStored() {\n return typeof sessionStorage !== \"undefined\" && Boolean(sessionStorage.getItem(LATENCY_KEY))\n}\n\n// At registration: a page that can use the simulator gets the module now —\n// effectively eager, in development only. No meta and no stored delay: no\n// module, no global handle, nothing on the page.\n//\n// This runtime registers with Turbo while it is being evaluated, BEFORE the\n// entry that imports it has run — before the default entry has handed the\n// module over, before the opt-in entry has said where to import it from. So\n// with neither in place this does nothing, and each entry calls it again\n// once it has done its part (see reactive_controller.js and core.js).\nexport function registerReactiveDev() {\n if (typeof document === \"undefined\") return\n if (__SPLIT__ && !featureModules.has(\"devtools\") && !FEATURES.get(\"devtools\")[1]) return\n const development = document.querySelector?.('meta[name=\"phlex-reactive-env\"]')?.content === \"development\"\n // (The default entry has the module in it: there is nothing to load, and\n // attach() is idempotent, so the second call is harmless.)\n if (!__SPLIT__) {\n if (development) devtoolsFeature.attach()\n } else if (development || latencyStored()) withFeature(\"devtools\", (devtools) => development && devtools.attach())\n}\n\n// --- Global reactive-activity signal (issue #201) --------------------------\n// A DOCUMENT-LEVEL count of in-flight reactive operations — the direct analogue\n// of Turbo's progress bar, but for reactive round trips and deferred renders\n// instead of navigations. Anything that starts an async reactive operation calls\n// enterReactiveActivity(); when it settles (success OR failure, on every path) it\n// calls exitReactiveActivity(). The count is exposed two ways so an app — or a\n// system test — can key off \"is the reactive layer settling?\" without knowing\n// about any individual root:\n//\n// * a marker on <html>: data-reactive-active present while count > 0 (CSS can\n// drive a global spinner; code/tests can read it). A DISTINCT name from the\n// per-root data-reactive-busy so a [data-reactive-busy] selector never also\n// matches the document element.\n// * events on document: reactive:busy on the 0 -> >0 edge, reactive:idle on the\n// >0 -> 0 edge — EDGES ONLY (not once per op), each carrying { count }.\n//\n// It sums ACROSS all reactive roots (module-level, not per-controller) and across\n// the two async lifecycles wired below:\n// * dispatch — entered in #applyBusy (at ENQUEUE, so the queue wait counts too),\n// exited in the settle closure #perform runs in its finally.\n// * defer — entered/exited with the pendingDefers registry (set/delete), the\n// ONE registry both the fetch (pull) and the stream (push) lane maintain — so\n// the push lane stays balanced even though it clears its pending markers by a\n// node swap, not clearDeferPending. A supersede is delete-then-set (net zero),\n// which is correct: a fast typist's replaced defer is still \"layer busy\".\n//\n// compute-seed is deliberately NOT counted: recompute() is synchronous, so a seed\n// is fully applied by the time the call returns — there is no async window to await\n// (the \"value settles a beat after a morph/seed\" case the issue describes is\n// covered by the System test helpers' re-resolve-by-id polling, not this counter).\nexport const ACTIVE_ATTR = \"data-reactive-active\"\n\nlet activityCount = 0\n\n// Increment the global in-flight count; on the 0 -> 1 edge, mark <html> and fire\n// reactive:busy. Fully defensive — a non-browser/test document with no\n// documentElement/dispatchEvent still tracks the count and simply skips the DOM\n// side effects (a global signal must never throw during bootstrap or a round trip).\nexport function enterReactiveActivity() {\n activityCount++\n if (activityCount === 1) syncReactiveActivity(\"reactive:busy\")\n}\n\n// Decrement the global in-flight count, clamped at 0 so an unbalanced exit can\n// never drive it negative (which would wedge the marker on forever). On the\n// 1 -> 0 edge, clear the <html> marker and fire reactive:idle.\nexport function exitReactiveActivity() {\n if (activityCount === 0) return\n activityCount--\n if (activityCount === 0) syncReactiveActivity(\"reactive:idle\")\n}\n\n// The current in-flight count — a test seam and a runtime read (an app can gate an\n// \"unsaved changes\" prompt on `reactiveActivityCount() > 0`).\nexport function reactiveActivityCount() {\n return activityCount\n}\n\n// Test seam: reset the module-level counter (and clear the marker) between tests,\n// since the module is imported once per bun run.\nexport function resetReactiveActivity() {\n activityCount = 0\n const root = typeof document !== \"undefined\" ? document.documentElement : null\n root?.removeAttribute?.(ACTIVE_ATTR)\n}\n\n// Write the <html> marker from the current count and fire the edge event on\n// document. Both sides are independently guarded so a partial document stub (a\n// documentElement without toggleAttribute, or a document without dispatchEvent)\n// degrades to a no-op rather than throwing.\nfunction syncReactiveActivity(eventName) {\n if (typeof document === \"undefined\") return\n const root = document.documentElement\n if (typeof root?.toggleAttribute === \"function\") {\n root.toggleAttribute(ACTIVE_ATTR, activityCount > 0)\n }\n if (typeof document.dispatchEvent === \"function\" && typeof CustomEvent === \"function\") {\n document.dispatchEvent(new CustomEvent(eventName, { detail: { count: activityCount } }))\n }\n}\n\n// --- Reactive request totals (issue #279) ----------------------------------\n// A running total of the reactive REQUESTS made, per kind — \"action\" (one per\n// #perform fetch) and \"defer\" (one per performDeferFetch) — so a system test can\n// assert \"one request on first open, none after\". The in-flight count above says\n// whether the layer is settling; this says how much it did.\n//\n// The JSON on <html data-reactive-requests> IS the store (read-modify-write per\n// request): a test reads it, and re-baselines by writing zeros, with no window\n// hook and no module state to drift from it. Written ONLY under the verbose gate\n// — data-reactive-verbose on <html> or on any reactive root (stamped in dev/test\n// by default) — so production writes nothing. The gate is checked once per\n// network request, never per event.\nexport const REQUESTS_ATTR = \"data-reactive-requests\"\n\nexport function countReactiveRequest(kind) {\n if (typeof document === \"undefined\") return\n const root = document.documentElement\n if (typeof root?.setAttribute !== \"function\" || !reactiveRequestsCounted(root)) return\n const totals = readReactiveRequests(root)\n totals[kind] = (Number(totals[kind]) || 0) + 1\n root.setAttribute(REQUESTS_ATTR, JSON.stringify(totals))\n}\n\nfunction reactiveRequestsCounted(root) {\n return root.hasAttribute?.(\"data-reactive-verbose\") || !!document.querySelector?.('[data-reactive-verbose=\"true\"]')\n}\n\n// The current totals, or zeros when the attribute is absent or not an object\n// (a hand-edited / garbled value restarts the count rather than throwing).\nfunction readReactiveRequests(root) {\n try {\n const parsed = JSON.parse(root.getAttribute(REQUESTS_ATTR))\n if (parsed && typeof parsed === \"object\") return { action: 0, defer: 0, ...parsed }\n } catch {}\n return { action: 0, defer: 0 }\n}\n\nexport function registerReactiveActions() {\n registerReactiveVisit()\n registerReactiveToken()\n registerReactiveJs()\n registerReactiveDefer()\n registerReactiveStreamRender()\n registerReactiveMorphFocus()\n registerReactiveModals()\n registerReactiveKeep()\n registerReactiveImageFallback()\n registerReactivePlaintext()\n registerReactiveOffline()\n registerReactiveDev()\n}\n\n// Escape a DOM id for safe interpolation into a RegExp (an id can legally contain\n// regex metacharacters like `.`/`:` — e.g. an `escape:`-namespaced or dotted id).\n// Used by #extractToken to match the stream that re-renders THIS element by id.\nexport function escapeRegExp(string) {\n return string.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")\n}\n\n// --- Loaded-twice guard (issue #275) ------------------------------------------\n// Each shipped entry carries its own copy of this file. An app that imports\n// BOTH phlex/reactive/reactive_controller and phlex/reactive/core (or bundles\n// one and pins the other) gets two runtimes: two request queues' worth of\n// module state, two activity counters, two feature tables. Say so, loudly.\nconst CORE_LOADED = Symbol.for(\"phlex-reactive.core\")\nif (globalThis[CORE_LOADED]) {\n console.error(\n \"[phlex-reactive] the client was loaded twice. Import EITHER phlex/reactive/reactive_controller (the default, \" +\n \"everything in one file) OR phlex/reactive/core (features on demand), not both — each carries its own state.\",\n )\n}\nglobalThis[CORE_LOADED] = true\n\n// --- Registration guard (issue #26 part 2) -------------------------------\n// In a `lazyLoadControllersFrom(\"controllers\", application)` app, only\n// controllers under app/javascript/controllers/ are registered. This module\n// lives outside that dir, so importing it isn't enough — `data-controller=\n// \"reactive\"` does NOTHING until the host runs application.register(\"reactive\",\n// ...). The failure is silent: components render, but no action ever fires.\n//\n// We can't warn from connect() in that case (connect never runs). Instead, once\n// the page is ready, if reactive elements exist but no controller has connected,\n// the controller wasn't registered — so we warn, pointing at the fix.\nlet reactiveConnected = false\n\nexport function checkReactiveRegistration() {\n if (reactiveConnected) return\n if (typeof document === \"undefined\") return\n const els = document.querySelectorAll('[data-controller~=\"reactive\"]')\n if (!els || els.length === 0) return\n console.warn(\n \"[phlex-reactive] found \" + els.length + ' element(s) with data-controller=\"reactive\" ' +\n \"but the reactive controller never connected. It is loaded but not registered — \" +\n 'add `application.register(\"reactive\", ReactiveController)` (importmap) or import it ' +\n \"into app/javascript/controllers/ for lazyLoadControllersFrom apps. See the README.\"\n )\n}\n\n// Test seams (no-ops in production usage).\n\nif (typeof window !== \"undefined\" && typeof document !== \"undefined\") {\n // Defer past initial controller connection (a microtask/tick after ready).\n const scheduleCheck = () => setTimeout(checkReactiveRegistration, 0)\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", scheduleCheck, { once: true })\n } else {\n scheduleCheck()\n }\n}\n\n// The interpret-time attribute-name allowlist (issue #96) — the SECOND half of\n// the two-sided default-deny. The Ruby builder already refuses these at build\n// time; this guards a hand-built / forged ops attr from bypassing it. Refused:\n// event handlers (on*, XSS), URL-bearing names (a javascript: navigation\n// surface), and style (CSS injection). Case-insensitive, mirroring js.rb.\n// srcset (issue #373) too: a URL list that picks what an <img> loads.\nconst REFUSED_ATTR_URL = new Set([\"href\", \"src\", \"srcset\", \"srcdoc\", \"action\", \"formaction\", \"xlink:href\", \"style\"])\nfunction attrRefused(name) {\n const lower = String(name).toLowerCase()\n return lower.startsWith(\"on\") || REFUSED_ATTR_URL.has(lower)\n}\n\n// Run an animated visibility change (issue #96 `transition:`). `flip` performs\n// the actual hidden-flag change; `[during, from, to]` are class lists applied\n// AROUND it. Cleanup is awaited via the element's OWN animationend/transitionend\n// OR a setTimeout fallback — whichever comes first — so an element with NO\n// animation never leaves the helper classes stuck (the op chain itself is not\n// blocked: cleanup is fire-and-forget, later ops run immediately). Both end\n// events bubble, so a descendant's is ignored, and settling drops both\n// listeners (the effects.js fix, #296). The fallback is armed synchronously,\n// not behind the frame, so a hidden tab (no rAF) still cleans up; cleanup then\n// cancels the pending frame and clears `from` too, so a late frame can't leave\n// `to` (or `from`) stuck. A one-shot `done` guard runs cleanup exactly once.\n// One run per element: a new run first settles the live one (its classes,\n// timer, listeners and frame), so a superseded run's late wakeups can't strip\n// the new run's classes. The fallback follows the element's computed\n// durations (transitionFallbackMs), so a 600 ms transition isn't cut at 350.\nconst transitionRuns = new WeakMap()\nfunction runTransition(el, transition, flip) {\n const [during, from, to] = transition\n transitionRuns.get(el)?.()\n el.classList.add(during, from)\n flip()\n let done = false\n const frame = requestAnimationFrame(() => {\n if (done) return\n el.classList.remove(from)\n el.classList.add(to)\n })\n\n const cleanup = (event) => {\n if (done || (event && event.target !== el)) return\n done = true\n transitionRuns.delete(el)\n clearTimeout(timer)\n globalThis.cancelAnimationFrame?.(frame)\n el.removeEventListener(\"animationend\", cleanup)\n el.removeEventListener(\"transitionend\", cleanup)\n el.classList.remove(during, from, to)\n }\n transitionRuns.set(el, cleanup)\n el.addEventListener(\"animationend\", cleanup)\n el.addEventListener(\"transitionend\", cleanup)\n // Also the ONLY path for a non-animated element (no end event fires there),\n // so it must always be scheduled — and never behind the frame.\n const timer = setTimeout(cleanup, transitionFallbackMs(el))\n}\n\n// The longest computed duration × iteration-count + delay over the transition\n// and animation lists (CSS repeats the shorter list; a transition runs once,\n// `infinite` hits the cap), plus 50 ms; 350 ms when nothing is declared (or\n// there is no computed style), capped at 5 s so a bogus value can't wedge the\n// classes on. Read once `during` is on the element.\nfunction transitionFallbackMs(el) {\n let longest = 0\n try {\n const style = getComputedStyle(el)\n for (const kind of [\"transition\", \"animation\"]) {\n const [durations, delays] = [\"Duration\", \"Delay\"].map((name) =>\n String(style[kind + name])\n .split(\",\")\n .map((v) => parseFloat(v) * (/ms/.test(v) ? 1 : 1000) || 0),\n )\n const counts = String(style[kind + \"IterationCount\"])\n .split(\",\")\n .map((v) => (/inf/.test(v) ? Infinity : parseFloat(v) >= 0 ? parseFloat(v) : 1))\n for (let i = 0; i < durations.length || i < delays.length || i < counts.length; i++) {\n const runs = durations[i % durations.length] * counts[i % counts.length] || 0\n longest = Math.max(longest, runs + delays[i % delays.length])\n }\n }\n } catch {}\n return Math.min(Math.max(longest + 50, 350), 5000)\n}\n\n// Rich-text and contenteditable fields, as #collectFields reads them (minus\n// the [name] guard — an editor's name may live on its IDL `name` getter:\n// Trix's `input=`-paired hidden input). The persist feature keeps its own copy\n// of these two constants (features never import the core); a test pins that\n// they stay equal.\nexport const EDITOR_SELECTOR =\n \":is(lexxy-editor, trix-editor, [contenteditable=''], [contenteditable=true], [contenteditable=plaintext-only])\"\nexport const EDITOR_TAGS = new Set([\"lexxy-editor\", \"trix-editor\"])\n\n// A reactive_editable's value (issue #351): its text with line breaks — a\n// <br> is one, a block element (the <div> a contenteditable=true Enter\n// inserts) starts and ends a line — \"\\r\\n\" normalised, ONE trailing newline\n// dropped (the <br> browsers keep at the end), and folded to spaces when the\n// field is single-line. Read from the DOM, not innerText: no layout, the same\n// on a detached render and under any white-space.\nexport function editableValue(el) {\n let text = \"\"\n // A block's edge asks for a line break; the next text or <br> pays it,\n // once, unless the text already ends in one.\n let line = false\n const add = (s) => {\n if (line && text && !text.endsWith(\"\\n\")) text += \"\\n\"\n line = false\n text += s\n }\n const walk = (node) => {\n for (const child of node.childNodes) {\n const block = /^(div|p|li)$/.test(child.localName)\n if (block) line = true\n if (child.nodeType === 3) child.data && add(child.data)\n else if (child.localName === \"br\") add(\"\\n\")\n else if (child.nodeType === 1) walk(child)\n if (block) line = true\n }\n }\n walk(el)\n text = text.replace(/\\r\\n?/g, \"\\n\").replace(/\\n$/, \"\")\n return el.getAttribute(\"data-reactive-editable\") === \"multi\" ? text : text.replace(/\\n/g, \" \")\n}\n\n// element -> the text of its last server render (issue #351): what Escape\n// reverts to and what dirty tracking diffs against. Seeded when the root\n// connects (or on first focus) and advanced by every morph of the element.\nconst serverTexts = new WeakMap()\n\nfunction seedServerText(el) {\n if (el?.hasAttribute?.(\"data-reactive-editable\") && !serverTexts.has(el)) serverTexts.set(el, editableValue(el))\n}\n\nexport function serverText(el) {\n seedServerText(el)\n return serverTexts.get(el)\n}\n\n// A RICH editor (lexxy/trix) is only ready once its custom element upgraded —\n// Trix defines its elements in a setTimeout after load — and exposes the\n// string `value` accessor; reading it before that yields \"\", which is issue\n// #8. A bare [contenteditable] is plain DOM and always ready.\nfunction collectorEditorReady(el) {\n return EDITOR_TAGS.has(el.localName) ? typeof el.value === \"string\" : true\n}\n\n// The client-op whitelist behind on_client (issue #95, extended in #96). Mirrors\n// Phlex::Reactive::JS's vocabulary; an op name not in this map is\n// warn-and-skipped by #applyOps (client-side default-deny — a stale or newer\n// ops attr must never break the page). Each op is a pure, local DOM mutation:\n// nothing is sent anywhere, and nothing is read back — with ONE deliberate\n// exception, paste_into (issue #228), which reads the clipboard behind the\n// browser's own gesture + permission gates and still only writes locally.\n// Frozen so nothing can be registered into it at runtime — extending the\n// vocabulary is a gem change, not an app hook.\nconst CLIENT_OPS = Object.freeze({\n show: (el, args, resolveTargets) => setVisibility(el, false, args, resolveTargets),\n hide: (el, args, resolveTargets) => setVisibility(el, true, args, resolveTargets),\n toggle: (el, args, resolveTargets) => setVisibility(el, !el.hidden, args, resolveTargets),\n add_class: (el, args) => el.classList.add(...(args.classes ?? [])),\n remove_class: (el, args) => el.classList.remove(...(args.classes ?? [])),\n toggle_class: (el, args) => (args.classes ?? []).forEach((c) => el.classList.toggle(c)),\n\n // Attribute ops (issue #96), interpret-time allowlisted. set_attr writes the\n // (already-stringified) value; toggle_attr adds a missing attr (value \"\") or\n // removes a present one — or, given values [on, off] (issue #271), flips\n // between them (an absent attr becomes `on`); remove_attr removes it. A\n // refused name warns + skips.\n set_attr: (el, args) => {\n if (guardAttr(args.name)) el.setAttribute(args.name, args.value ?? \"\")\n },\n remove_attr: (el, args) => {\n if (guardAttr(args.name)) el.removeAttribute(args.name)\n },\n toggle_attr: (el, args) => {\n if (!guardAttr(args.name)) return\n if (Array.isArray(args.values)) {\n const [on, off] = args.values\n el.setAttribute(args.name, el.getAttribute(args.name) === on ? off : on)\n } else if (el.hasAttribute(args.name)) el.removeAttribute(args.name)\n else el.setAttribute(args.name, \"\")\n },\n\n // Focus ops (issue #96). focus targets the match itself; focus_first targets\n // its first focusable descendant (opened-menu → first menuitem).\n focus: (el) => el.focus?.(),\n focus_first: (el) => firstFocusable(el)?.focus?.(),\n // Issue #399: blur the focused element when it is the target or inside it\n // (Escape ends a menu's :focus-within); focus elsewhere is left alone.\n // ACTOR-ONLY like focus (BROADCAST_REFUSED_OPS server-side).\n blur: (el) => {\n const active = el.ownerDocument?.activeElement ?? globalThis.document?.activeElement\n if (active && (active === el || el.contains?.(active))) active.blur?.()\n },\n\n // Modal dialog ops (issue #365): HTMLDialogElement#showModal / #close.\n // ACTOR-ONLY like focus (BROADCAST_REFUSED_OPS server-side).\n show_modal: (el) => showModal(el),\n close_dialog: (el) => closeDialog(el),\n\n // Text op (issue #159): set textContent — XSS-safe by construction (never\n // innerHTML), strictly less powerful than set_attr. Change-guarded like\n // #mirrorText. With global: true it is the cross-root text escape: paint a\n // value into a recap node OUTSIDE the component's root.\n text: (el, args) => {\n const text = String(args.value ?? \"\")\n if (el.textContent !== text) el.textContent = text\n },\n\n // Copy a server-rendered image (issue #373): src, srcset and alt from the\n // `from` <img>, resolved with the op's own scoping (the `submitter:`\n // precedent). The URL is read off that element, never from an argument —\n // the src refusal above stays. A selector that does not parse (a URL\n // string) is a miss, like one that matches nothing.\n copy_media: (el, args, resolveTargets) => {\n let source = null\n try {\n source = resolveTargets({ ...args, to: args.from })[0] ?? null\n } catch {}\n copyMedia(el, source)\n },\n\n // Tick or untick a whole checkbox group (issue #342) — \"clear selection\".\n // The bindings feature flips the root's OWNED boxes the way its select-all\n // header does (every box set, then input + change on each flipped one) and\n // re-syncs the group bindings once. global: true ignores the op's own root\n // and asks every reactive root on the page; each flips only the boxes it\n // owns (nested roots included), so every root holding the group answers.\n check_group: (el, args) => {\n const roots = args.global ? document.querySelectorAll('[data-controller~=\"reactive\"]') : [el]\n for (const root of roots) {\n __SPLIT__\n ? withFeature(\"bindings\", (bindings) => bindings.checkGroup(root, args.group, args.checked))\n : bindingsFeature.checkGroup(root, args.group, args.checked)\n }\n },\n\n // Write this tab's presence state (issue #358) through the root's\n // presence_set action. The feature warns outside a reactive_presence root.\n presence: (el, args) =>\n __SPLIT__\n ? withFeature(\"presence\", (presence) => presence.set(el, args.state))\n : presenceFeature.set(el, args.state),\n\n // Dispatch a bubbling CustomEvent (issue #96). RAW element.dispatchEvent — the\n // controller SHADOWS Stimulus's this.dispatch helper, so it must not be used.\n dispatch: (el, args) => {\n el.dispatchEvent(new CustomEvent(args.name, { bubbles: true, composed: true, detail: args.detail ?? {} }))\n },\n\n // Submit the target's OWN form (issue #226) via requestSubmit() — constraint\n // validation runs and a REAL cancelable `submit` event fires, so an\n // on(:action, event: \"submit\") interception or a native/Turbo form handles it\n // exactly like a user submit. No form → no-op. ACTOR-ONLY like focus: the\n // broadcast builder refuses it server-side (BROADCAST_REFUSED_OPS).\n // `submitter:` (issue #319) resolves with the op's own scoping (the\n // `expanded:` precedent) and submits THROUGH it, so its name=value posts. It\n // must be a submit control of that form — requestSubmit(x) throws otherwise —\n // so anything else warns and falls back to a plain requestSubmit().\n submit: (el, args, resolveTargets) => {\n const form = submitFormFor(el)\n if (!form) return\n let submitter = null\n if (args?.submitter != null && typeof resolveTargets === \"function\") {\n submitter = resolveTargets({ ...args, to: args.submitter })[0] ?? null\n if (!(submitter?.form === form && (submitter.type === \"submit\" || submitter.type === \"image\"))) {\n console.warn(`[phlex-reactive] submitter ${args.submitter} is not a submit control of the form — ignored`)\n submitter = null\n }\n }\n submitter ? form.requestSubmit?.(submitter) : form.requestSubmit?.()\n },\n\n // Clipboard-source paste (issue #228): on a user gesture, read\n // navigator.clipboard.readText() and feed the text into the target field\n // through the normal input pipeline — exactly what a native Cmd/Ctrl+V does.\n // The ONE op that reads a browser API and is async: fire-and-forget, so\n // applyOps stays sync and chain siblings never wait (the runTransition\n // posture). A rejected/dismissed read, empty text, or a missing API is a\n // SILENT no-op — page state must not change. ACTOR-ONLY like focus/submit:\n // the broadcast builder refuses it server-side (BROADCAST_REFUSED_OPS) —\n // and that server gate is the REAL one. The browser only partially backs it\n // up: Safari gates every read on a fresh gesture and Firefox shows its\n // paste picker per read, but Chromium's clipboard-read is a PERSISTENT\n // per-origin permission — once granted (the legit paste button itself\n // induces that), readText() succeeds with no gesture. No client-side\n // refusal is possible here: the reactive:js interpreter cannot distinguish\n // an actor reply's stream from a broadcast's, and reply.js legitimately\n // carries this op.\n paste_into: (el) => pasteClipboardInto(el),\n\n // Preview a chosen file (issue #375): paint the input's first image file\n // onto the `into` <img> through an object URL made HERE from the input's\n // own File. The op carries no URL and `args.url` is never read, so the src\n // refusal of set_attr (issue #96) stays whole. ACTOR-ONLY like paste_into\n // (BROADCAST_REFUSED_OPS server-side).\n preview_file: (el, args, resolveTargets) => previewFile(el, args, resolveTargets),\n\n // A field's live value (issue #374): the paste_into write without the\n // clipboard or the focus. ACTOR-ONLY (BROADCAST_REFUSED_OPS server-side).\n set_value: (el, args) => setFieldValue(el, String(args.value ?? \"\")),\n\n // Client-only drafts (issue #239): merge a flat state bag into the root's\n // reactive_persist draft / forget the draft. ACTOR-ONLY like focus/submit\n // (BROADCAST_REFUSED_OPS server-side) — rewriting or wiping every\n // subscriber's draft from a broadcast would be hostile.\n // The draft code lives in the persist feature module (issue #275), so both\n // ops run once it has loaded — at once on a root that restored a draft.\n persist_state: (el, args) =>\n __SPLIT__\n ? withFeature(\"persist\", (persist) => persist.writeState(el, args.state))\n : persistFeature.writeState(el, args.state),\n persist_clear: (el) =>\n __SPLIT__ ? withFeature(\"persist\", (persist) => persist.clearRoot(el)) : persistFeature.clearRoot(el),\n})\n\n// The form a submit op commits (issue #226), in order: the target itself when\n// it IS a form (tagName, not instanceof — fake-node/test friendly), its form\n// owner for a control (input.form — honors a form= attribute), else the nearest\n// ancestor form. closest() may cross the component root by design — the\n// FIELD'S OWN form is the submit scope, not the reactive boundary.\nfunction submitFormFor(el) {\n if (el?.tagName === \"FORM\") return el\n return el?.form ?? el?.closest?.(\"form\") ?? null\n}\n\n// Read the clipboard into a field (issue #228) — the body of the paste_into\n// op. The write mirrors a native paste: set .value, dispatch a bubbling\n// `input` event (the set-value + dispatch contract, issue #183 — compute\n// reducers, show bindings, and on_complete all run exactly as if the user\n// had typed), then focus (the caret lands where the user continues typing on\n// a partial paste). Availability-guarded: insecure contexts and some\n// webviews have no navigator.clipboard — the connect()-time gate hides\n// marked triggers there, so this guard is belt-and-braces. Empty text is a\n// no-op: \"paste nothing\" must not clear a half-typed field.\nfunction pasteClipboardInto(field) {\n const clipboard = globalThis.navigator?.clipboard\n if (typeof clipboard?.readText !== \"function\") return\n clipboard\n .readText()\n .then((text) => {\n if (!text) return\n field.value = text\n dispatchInput(field)\n field.focus?.()\n })\n .catch(() => {\n // Permission denied or the prompt dismissed — the browser's own UX said\n // no. The issue-#228 contract: a silent no-op, never an error.\n })\n}\n\n// --- Preview a chosen file (issue #375) ---\n// img -> the object URL preview_file last made for it. The runtime owns it:\n// never an op argument, never an attribute a caller sets. An entry lives\n// while the img is on the page AND still shows that URL; a re-pick, a clear,\n// a non-image file, the img leaving the page or a morph that strips its src\n// in place revokes it. One MutationObserver notices the last two, and only\n// while some preview is live: it then wakes on every DOM mutation of the page\n// (a live feed, a drag), and each wake is a walk over the few live previews.\nconst previews = new Map()\nlet previewObserver = null\n\nfunction previewFile(input, args, resolveTargets) {\n if (input?.tagName !== \"INPUT\" || input.type !== \"file\") {\n return warnPreview(`reads an input[type=file], not <${input?.localName}>`)\n }\n const img = typeof resolveTargets === \"function\" ? resolveTargets({ ...args, to: args.into })[0] : null\n // Duck-typed on tagName (the isDialog precedent): only an <img> ever gets a\n // src here — never an iframe, a script, a frame or anything else.\n if (img?.tagName !== \"IMG\") {\n return warnPreview(`paints an <img>, and ${JSON.stringify(args.into)} is ${img ? `a <${img.localName}>` : \"not on the page\"}`)\n }\n // valid: true (issue #382): an input the app marked invalid (setCustomValidity,\n // required) previews as empty. validity.valid, not checkValidity(): no\n // `invalid` event fired into the app's own listeners.\n const files = [...(args.valid === true && input.validity?.valid === false ? [] : (input.files ?? []))]\n const accepts = previewAccepts(input, args.accept)\n const listed = files.filter((f) => typeof f?.type === \"string\" && accepts(f))\n // Only an image is painted: a listed PDF clears the preview, unrefused.\n let file = listed.find((f) => f.type.startsWith(\"image/\"))\n // A refused file (issue #391) clears like an empty pick and says why. The\n // input keeps its files and its validity: the page owns the reset. Size is\n // read BEFORE an object URL exists, so an over-size file never gets one.\n let reason = files.length > 0 && listed.length === 0 ? \"type\" : null\n if (file && Number.isInteger(args.max_bytes) && file.size > args.max_bytes) reason = \"size\"\n const refused = reason === \"size\" ? file : files[0]\n if (reason) file = null\n forgetPreview(img)\n if (file) {\n const url = URL.createObjectURL(file)\n previews.set(img, url)\n img.setAttribute(\"src\", url)\n watchPreviews()\n } else img.removeAttribute(\"src\")\n resetImage(img)\n if (args.wrap != null) {\n const wraps = resolveTargets({ ...args, to: args.wrap })\n if (wraps.length === 0) warnPreview(`wrap: ${JSON.stringify(args.wrap)} is not on the page`)\n for (const wrap of wraps) file ? showWrap(wrap) : hideWrap(wrap)\n }\n if (args.name != null) {\n for (const node of resolveTargets({ ...args, to: args.name })) node.textContent = file ? file.name : \"\"\n }\n if (reason) {\n input.dispatchEvent(new CustomEvent(\"reactive:file-rejected\", { bubbles: true, detail: { reason, file: refused } }))\n }\n}\n\n// Which files the page takes: an image by default. accept: true reads the\n// input's own accept attribute; a string is that list for this op. A token is\n// an extension (\".png\"), a MIME type or a type/* wildcard, the browser's own\n// accept grammar. An absent or empty list keeps the image rule.\nfunction previewAccepts(input, accept) {\n const tokens = String((accept === true ? input.getAttribute(\"accept\") : typeof accept === \"string\" && accept) || \"\")\n .toLowerCase()\n .split(\",\")\n .map((token) => token.trim())\n .filter(Boolean)\n if (tokens.length === 0) return (f) => f.type.startsWith(\"image/\")\n return (f) => {\n const type = f.type.toLowerCase()\n return tokens.some((token) =>\n token[0] === \".\"\n ? String(f.name).toLowerCase().endsWith(token)\n : token.endsWith(\"/*\")\n ? type.startsWith(token.slice(0, -1))\n : type === token,\n )\n }\n}\n\nfunction warnPreview(why) {\n console.warn(`[phlex-reactive] preview_file ${why} — skipped`)\n}\n\n// Revoke the URL preview_file made for `img`, if it made one.\nfunction forgetPreview(img) {\n const url = previews.get(img)\n if (url === undefined) return\n previews.delete(img)\n URL.revokeObjectURL(url)\n if (previews.size === 0) {\n previewObserver?.disconnect()\n previewObserver = null\n }\n}\n\nfunction watchPreviews() {\n if (previewObserver || typeof MutationObserver !== \"function\" || !document?.documentElement) return\n previewObserver = new MutationObserver(() => {\n for (const [img, url] of previews) if (!img.isConnected || img.getAttribute(\"src\") !== url) forgetPreview(img)\n })\n previewObserver.observe(document.documentElement, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: [\"src\"],\n })\n}\n\n\n// The set-value contract's dispatch (issue #183): a bubbling `input`, so\n// compute reducers, show bindings and on_complete run as if the user typed.\nfunction dispatchInput(field) {\n if (typeof field.dispatchEvent === \"function\") field.dispatchEvent(new Event(\"input\", { bubbles: true }))\n}\n\n// The body of the set_value op (issue #374). Writes the IDL .value — never the\n// attribute, never HTML — and dispatches `input` only when the field's value\n// actually moved (compared after the write: a textarea normalizes CRLF, a\n// select ends at \"\" for a value no option has). No `change`, no focus. Only an\n// input, textarea or select is a target, so a contenteditable (reactive_editable's\n// to write) warns and skips like any other node. A file input takes \"\" alone —\n// the browser throws on anything else.\nfunction setFieldValue(field, value) {\n if (!/^(input|textarea|select)$/.test(field.localName) || (field.type === \"file\" && value !== \"\")) {\n console.warn(\"[phlex-reactive] set_value skipped: not a field it can write\", field)\n return\n }\n const before = fieldState(field)\n field.value = value\n if (fieldState(field) !== before) dispatchInput(field)\n}\n\n// What set_value compares: .value, or for a <select multiple> its whole\n// selection — assigning .value deselects every other option, which .value\n// (the first selected) alone would not show.\nfunction fieldState(field) {\n return field.type === \"select-multiple\" ? [...field.selectedOptions].map((o) => o.value).join(\"\\n\") : field.value\n}\n\n// Apply a hidden-flag change, optionally animated by a [during, from, to]\n// transition (issue #96). Split out so show/hide/toggle share it.\nfunction setHidden(el, hidden, args) {\n if (args?.transition) runTransition(el, args.transition, () => (el.hidden = hidden))\n else el.hidden = hidden\n}\n\n// show/hide/toggle (issue #271): flip visibility, then mirror the INTENDED\n// state into the `expanded:` target's aria-expanded — computed before the flip,\n// so a transition never delays or races it. The expanded target resolves with\n// the op's own scoping (root-scoped, or document-wide under global: true).\nfunction setVisibility(el, hidden, args, resolveTargets) {\n setHidden(el, hidden, args)\n if (args?.expanded == null || typeof resolveTargets !== \"function\") return\n for (const target of resolveTargets({ ...args, to: args.expanded })) {\n target.setAttribute?.(\"aria-expanded\", String(!hidden))\n }\n}\n\n// The interpret-time attribute guard: refuse (warn + skip) a name off the\n// allowlist. Returns true when the op may proceed.\nfunction guardAttr(name) {\n if (!attrRefused(name)) return true\n console.warn(`[phlex-reactive] refused client attr op on ${JSON.stringify(name)} — skipped`)\n return false\n}\n\n// Copy src, srcset and alt from one <img> onto another (issue #373) — the\n// copy_media op and the reactive_media binding (the bindings feature reaches it\n// through the core handle). Only an <img> target is written, so an iframe,\n// script, object, embed or frame never receives a src from here. A source that\n// is missing or not an <img> clears the target. An <img> source without src is\n// a hit with no image (issue #381): the target loses src and srcset but takes\n// the alt, so a known entry without a logo keeps its name. Each attribute is\n// written only when it changes, so the same URL twice does not reload. Returns\n// the alt copied, false when it cleared, or null when the target was skipped.\nfunction copyMedia(target, source) {\n if (target?.localName !== \"img\") {\n console.warn(`[phlex-reactive] copy_media targets an <img>, not <${target?.localName}> — skipped`)\n return null\n }\n const img = source?.localName === \"img\" ? source : null\n const src = img?.getAttribute(\"src\")\n let changed = false\n for (const name of [\"srcset\", \"src\"]) {\n const value = src ? img.getAttribute(name) : null\n if (value == null) {\n if (target.hasAttribute(name)) changed = target.removeAttribute(name) ?? true\n } else if (target.getAttribute(name) !== value) changed = target.setAttribute(name, value) ?? true\n }\n if (changed) resetImage(target)\n const alt = img?.getAttribute(\"alt\") ?? \"\"\n if (target.getAttribute(\"alt\") !== alt) target.setAttribute(\"alt\", alt)\n return img ? alt : false\n}\n\n// The first focusable descendant of `el`, in document order — the natural\n// keyboard target inside an opened menu/dialog. Covers the standard focusable\n// set; :not([tabindex=\"-1\"]) drops explicitly-removed nodes. Returns null when\n// nothing inside is focusable (focus_first then no-ops).\nconst FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])'\nfunction firstFocusable(el) {\n return el.querySelectorAll?.(FOCUSABLE)?.[0] ?? null\n}\n\n// Parse a [[name, args], ...] op list from a raw attr/param. An array passes\n// through; a JSON string is parsed; anything malformed degrades to [] — a bad\n// ops attr must NEVER break the page (client-side default-deny). Shared by the\n// controller's runOps and the reactive:js stream action (issue #97).\nfunction parseOps(raw) {\n if (Array.isArray(raw)) return raw\n if (typeof raw !== \"string\") return []\n try {\n const list = JSON.parse(raw)\n return Array.isArray(list) ? list : []\n } catch {\n return []\n }\n}\n\n// --- on_client binding records (issue #271) -----------------------------------\n// Each on_client call emits ONE record ({on, ops, window?, outside?, once?,\n// detail?, confirm?, confirmWhen?}); mix space-joins several onto one element (spaces inside a\n// record ride as \\u0020, so the join is unambiguous). Stimulus hands every\n// runOps descriptor on an element the SAME event.params, so runOps selects the\n// record(s) whose descriptor matches the firing event: event.type, the key\n// filter, and window-boundness.\n\n// Stimulus's default keyMappings (letters and digits map to themselves), plus\n// delete and backspace (issue #366): Stimulus lacks those two, so a shortcut\n// naming one binds a bare keydown and only shortcutApplies matches it. A\n// custom Stimulus schema is not mirrored — an unknown filter never matches.\nconst KEY_FILTER_MAP = Object.freeze({\n backspace: \"Backspace\",\n delete: \"Delete\",\n enter: \"Enter\",\n tab: \"Tab\",\n esc: \"Escape\",\n space: \" \",\n up: \"ArrowUp\",\n down: \"ArrowDown\",\n left: \"ArrowLeft\",\n right: \"ArrowRight\",\n home: \"Home\",\n end: \"End\",\n page_up: \"PageUp\",\n page_down: \"PageDown\",\n})\nconst KEY_FILTER_MODIFIERS = Object.freeze([\"meta\", \"ctrl\", \"alt\", \"shift\"])\n\n// Stimulus's rule: the four modifiers must match EXACTLY (ctrl+k is not k);\n// the remaining token is compared to event.key case-insensitively. A\n// non-keyboard event (no event.key) checks the modifiers only. `keyMappings`\n// is the app's Stimulus schema table (custom keys included) when the\n// controller has one; the default table is the fallback.\nfunction keyFilterMatches(filter, event, keyMappings) {\n const parts = filter.split(\"+\")\n for (const mod of KEY_FILTER_MODIFIERS) {\n if (parts.includes(mod) !== Boolean(event[`${mod}Key`])) return false\n }\n const token = parts.find((part) => !KEY_FILTER_MODIFIERS.includes(part))\n if (token === undefined || typeof event.key !== \"string\") return true\n const key =\n keyMappings && Object.hasOwn(keyMappings, token)\n ? keyMappings[token]\n : Object.hasOwn(KEY_FILTER_MAP, token)\n ? KEY_FILTER_MAP[token]\n : /^[a-z0-9]$/.test(token)\n ? token\n : null\n return typeof key === \"string\" && key.toLowerCase() === event.key.toLowerCase()\n}\n\n// Issue #357: a shortcut: (\"mod+z\", or { keys, inEditable }) is a window\n// keydown whose `mod` is meta on Apple platforms and ctrl elsewhere. Ruby\n// renders a meta twin and a ctrl twin; Stimulus filters the key, and this\n// decides whether the twin that fired is this platform's and whether the\n// target is one the user is typing in. Either miss is a complete no-op. A\n// delete/backspace chord (issue #366) has no twins and no Stimulus filter:\n// its bare keydown hears every key, and this check alone picks the chord.\n// (Stimulus typecasts a digit shortcut, \"1\", to a number: hence String().)\nconst shortcutKeys = (spec) => (spec && typeof spec === \"object\" ? spec.keys : String(spec))\n// A shortcut record keeps a bare `on: \"keydown\"`, so bindingMatches lets every\n// keydown through and this check, per record, picks the chord that fired.\nfunction shortcutApplies(spec, event, keyMappings) {\n const target = event.target\n if (spec?.inEditable !== true && (target?.isContentEditable || /^(input|textarea|select)$/.test(target?.localName)))\n return false\n const mod = /[Mm]ac|iP/.test(navigator.userAgentData?.platform ?? navigator.platform) ? \"meta\" : \"ctrl\"\n return keyFilterMatches(shortcutKeys(spec).replace(/^mod\\+/, `${mod}+`), event, keyMappings)\n}\n\n// Parse data-reactive-ops-param into binding records. Stimulus typecasts a\n// lone record to an object; several records stay a string (JSON.parse fails on\n// the join) and are split here. A LEGACY [[op, args]] list (array or JSON\n// string — a hand-built attr) becomes one record flagged `legacy`: it has no\n// `on`, so it always matches, and runOps reads its flags from event.params. A\n// malformed piece warns and is skipped; its siblings still run (default-deny).\nfunction parseBindingRecords(raw) {\n if (Array.isArray(raw)) return [{ ops: raw, legacy: true }]\n if (raw && typeof raw === \"object\") return [raw]\n if (typeof raw !== \"string\") return []\n const text = raw.trim()\n if (text.startsWith(\"[\")) return [{ ops: parseOps(text), legacy: true }]\n const records = []\n for (const piece of text.split(\" \")) {\n if (piece === \"\") continue\n let record = null\n try {\n record = JSON.parse(piece)\n } catch {\n record = null\n }\n if (record && typeof record === \"object\" && !Array.isArray(record)) records.push(record)\n else console.warn(`[phlex-reactive] malformed on_client binding record ${JSON.stringify(piece)} — skipped`)\n }\n return records\n}\n\n// Does this record's descriptor match the firing event? The window check\n// matters for an element carrying both `click` and `click@window`: one inside\n// click reaches both listeners, and only currentTarget tells them apart. It is\n// recognised structurally (isWindow), so an undefined currentTarget is never\n// window-bound and a replay built for another document's window still counts\n// (issue #303). Only a LEGACY record (no `on` by\n// construction) matches everything; any other record without a descriptor is\n// malformed and never matches (default-deny). `keyMappings === null` skips the\n// key filter (the caller's single-candidate shortcut).\nfunction bindingMatches(record, event, keyMappings) {\n if (record.legacy) return true\n const on = record.on\n if (typeof on !== \"string\" || on === \"\") return false\n const dot = on.indexOf(\".\")\n if (event.type !== (dot < 0 ? on : on.slice(0, dot))) return false\n if (Boolean(record.window) !== isWindow(event.currentTarget)) return false\n return dot < 0 || keyMappings === null || keyFilterMatches(on.slice(dot + 1), event, keyMappings)\n}\n\n// Issue #346: a record's `detail` lists top-level keys of event.detail and the\n// JSON scalar each must strictly equal — on_client(\"turbo:submit-end\", ops,\n// detail: { success: true }) runs only after a successful submit. A missing or\n// non-object event.detail, or a missing key, never matches (default-deny); a\n// record without `detail` matches whatever the event carries.\nfunction detailMatches(record, event) {\n const expected = record.detail\n if (expected === undefined) return true\n if (expected === null || typeof expected !== \"object\" || Array.isArray(expected)) return false\n const detail = event.detail\n if (detail === null || typeof detail !== \"object\") return false\n // A missing key reads undefined, which no JSON scalar (null included) equals.\n return Object.keys(expected).every((key) => detail[key] === expected[key])\n}\n\n// A `once: true` record (PR #272) is spent after its first run. Stimulus\n// removes only that binding's own listener; a regular same-event sibling on\n// the element keeps calling runOps with BOTH records, so the spent one is\n// skipped here. Spent state belongs to the TRIGGER element (a WeakMap, so it\n// is collected with it): a morph/stream that swaps in a fresh element starts\n// fresh, and a byte-identical sibling trigger has its own. A window-bound\n// binding's currentTarget is the window, so there the element's ops attr joins\n// the key (a re-rendered window-bound once trigger with the same markup stays\n// spent — a known limit; spentEarlyOnce below has the same one for a replayed\n// `:once` hotkey).\nconst spentOnceBindings = new WeakMap()\nfunction onceBindingSpent(controller, event, record) {\n if (!record.once) return false\n const owner = event.currentTarget ?? controller.element\n let byOwner = spentOnceBindings.get(controller)\n if (!byOwner) {\n byOwner = new WeakMap()\n spentOnceBindings.set(controller, byOwner)\n }\n let spent = byOwner.get(owner)\n if (!spent) {\n spent = new Set()\n byOwner.set(owner, spent)\n }\n const raw = event.params?.ops\n const attr = owner === globalThis.window ? `${typeof raw === \"string\" ? raw : JSON.stringify(raw ?? null)}|` : \"\"\n const key = attr + JSON.stringify(record)\n if (spent.has(key)) return true\n spent.add(key)\n return false\n}\n\n// Two identical descriptors on one element (two mix-ed on_client calls with\n// the same event) are two Stimulus bindings, so runOps runs twice for ONE\n// event. The first call runs every matching record; a repeat is a no-op.\n// \"Repeat\" is (controller, listener target, the element's ops attr): Stimulus\n// walks EVERY binding of one window listener with the SAME event object, so\n// keying on currentTarget alone (= window) would drop every other root's\n// window-bound binding — and a sibling element's within one root. The ops attr\n// is the per-element key (a lone record is typecast to a fresh object per\n// binding, so it is compared by its JSON). Two elements in one root carrying\n// byte-identical window-bound records run once — the same ops, applied once.\nconst ranBindings = new WeakMap()\nfunction bindingsAlreadyRan(event, controller) {\n if (event === null || typeof event !== \"object\") return false\n const raw = event.params?.ops\n const key = typeof raw === \"string\" ? raw : JSON.stringify(raw ?? null)\n let byController = ranBindings.get(event)\n if (!byController) {\n byController = new WeakMap()\n ranBindings.set(event, byController)\n }\n let byTarget = byController.get(controller)\n if (!byTarget) {\n byTarget = new Map()\n byController.set(controller, byTarget)\n }\n let keys = byTarget.get(event.currentTarget)\n if (!keys) {\n keys = new Set()\n byTarget.set(event.currentTarget, keys)\n }\n if (keys.has(key)) return true\n keys.add(key)\n return false\n}\n\n// Interpret a [[name, args], ...] op list against the frozen CLIENT_OPS\n// whitelist (issues #95/#96/#97). `resolveTargets(args)` returns the element(s)\n// an op applies to — the controller scopes to its root (excluding nested\n// reactive roots); the reactive:js stream action scopes to its target root (or\n// the document). An unknown name warns and is SKIPPED while the rest of the\n// chain still applies — client-side default-deny, one bad op never takes down\n// its siblings. Object.hasOwn (not a bare read) so inherited Object members\n// (\"constructor\") can't masquerade as ops.\nfunction applyOps(list, resolveTargets, onZeroTargets) {\n for (const entry of list) {\n if (!Array.isArray(entry)) continue\n const [name, args = {}] = entry\n if (!Object.hasOwn(CLIENT_OPS, name)) {\n console.warn(`[phlex-reactive] unknown client op ${JSON.stringify(name)} — skipped`)\n continue\n }\n const targets = resolveTargets(args)\n if (targets.length === 0 && onZeroTargets) onZeroTargets(name, args)\n for (const el of targets) {\n CLIENT_OPS[name](el, args, resolveTargets)\n if (args?.keep === true) keepResult(el, name, args, resolveTargets)\n }\n }\n}\n\n// Resolve a reactive:js op's targets against its `target` root (issue #97).\n// \"@root\" is the root element itself; a selector resolves WITHIN it; a bare\n// selector with no root (no `target` attr on the stream) resolves document-wide\n// — a broadcast op anchored by a global selector (#bell) rather than a\n// component. Unlike the controller path there is no nested-reactive-root\n// ownership filter: a server-pushed op names its own scope explicitly —\n// including `global: true`, which opts a single op out of the target-root\n// scope to document-wide resolution (issue #159; the same escape the builder\n// documents for the controller path).\nfunction streamOpTargets(args, root) {\n const to = args.to\n if (root) {\n if (to === \"@root\") return [root]\n if (typeof to !== \"string\" || to === \"\") return []\n if (args.global) return [...document.querySelectorAll(to)]\n return [...root.querySelectorAll(to)]\n }\n // No target root: document-scoped. \"@root\" is meaningless here (nothing to\n // anchor to) → no-op; a selector matches document-wide.\n if (typeof to !== \"string\" || to === \"\" || to === \"@root\") return []\n return [...document.querySelectorAll(to)]\n}\n\n// --- Feature modules (issue #275) ---------------------------------------------\n// The client is a small core plus feature modules a page loads only when one\n// of its roots asks for them. Each entry is [needs, load, waiting?, gates?]:\n//\n// needs(root) a cheap marker read — does this root use the feature?\n// null for a feature no root asks for (it is loaded by\n// something page-level instead).\n// load() how to import the module — NOT written here. core.js,\n// the opt-in entry, supplies one per feature\n// (registerReactiveFeatureLoader): a LITERAL\n// import(\"phlex/reactive/features/<name>\"), so a bundler can\n// see the specifier and the import map resolves it to its\n// own digested file. The default entry supplies none: its\n// features are part of the file.\n// waiting(root, pending)\n// optional, and like load() supplied by core.js (only the\n// opt-in client ever waits): what must not be missed WHILE\n// the import is on its way. Runs when a root starts waiting, RECORDS into\n// `pending` (a plain object the feature receives later) and\n// returns its own undo. Core bytes — record, never act.\n// gates optional, true when the feature's connect changes what a\n// REQUEST reads (the draft restore writes the fields a\n// request collects). A root's action requests wait for its\n// features only while one that gates is still loading; a\n// feature that does not gate never delays a request.\n//\n// TABLE ORDER IS CONNECT ORDER (and disconnect order): persist is first\n// because its restore writes the values every other connect-time seed reads.\n//\n// A feature module exports\n//\n// install(shared) optional; once, when it has loaded\n// connect(controller, core, morphed, pending)\n// once per root connection; `pending` is what\n// its `waiting` hook recorded (undefined when\n// the root never had to wait)\n// disconnect(controller, core)\n// FIRST in the controller's disconnect()\n// abandon(root, pending) optional; the root left BEFORE the feature\n// could connect — last chance to act on\n// what was recorded\n//\n// and never imports this file (two copies of the core would split its state).\n// `shared` is FEATURE_SHARED below: the core's single-instance counters.\n// Everything a feature needs from one controller arrives as `core`, the\n// handle #featureCore builds — the ONE door into its private state:\n//\n// core.emit(name, detail, options) raw-dispatch a lifecycle event\n// core.reseed() re-run the connect-time seeds that\n// read field values\n// core.proceed(target, action, params) send an action down the ordinary\n// pipeline (veto, queue, request)\n// core.forgetToken() drop the token cached from a reply\n// core.owns(el) is this control this root's own, not\n// a nested reactive root's (issue #15)\n// core.ownership() the hoisted per-pass form of owns\n// core.opTargets(args) the elements an op's `to:` names\n// core.applyOps(list, defaultTo) run an op list root-scoped\n// core.diagnose(label, args) the zero-target warning\n// core.listnavOptions(event) the keyboard-navigable options\n// core.collectFields() this root's fields, as a request\n// would send them\n// core.confirm(message, context) the confirm gate (resolves a boolean)\n//\n// Later features add to that handle; nothing else of the controller is theirs\n// to touch beyond its public surface (element, application, …).\n//\n// WHEN A FEATURE CONNECTS. A module that is already here connects inside\n// connect() itself, in the same task, before the early-queue drain. In the\n// default bundle (phlex/reactive/reactive_controller) that is every feature,\n// always: they are handed over before anything connects, and nothing below\n// about imports, waiting, gates or timeouts ever applies.\n//\n// With phlex/reactive/core alone, the first root on a page that needs a\n// feature waits for its import: connect() only starts it, and the feature\n// connects when the module has arrived — after every feature EARLIER in the\n// table that this root is also waiting for (table order), never after a later\n// one: a slow defer import does not hold the draft restore back. Once the\n// module is loaded (a later root, the next Turbo visit, a reconnect) it too\n// connects inside connect().\n//\n// THE DEFAULT ENTRY HAS NONE OF THIS (issue #305). It is built with\n// `__SPLIT__` false: the runtime imports every feature statically and calls\n// it directly — connect() connects what the root's markers ask for, in this\n// order, by name in the source and by nothing at all in the minified file.\n// No table, no loader, no import(): what is below is the opt-in entry's.\n\n// The marker checks of the features that connect per root. The default entry\n// calls them directly (#connectFeaturesNow); the table below hands them to\n// the loader.\nfunction persistNeeded(root) {\n const declared = root.getAttribute?.(\"data-reactive-persist\")\n return Boolean(declared) && declared !== \"off\"\n}\n\n// reply.defer, reactive_lazy, reactive_lazy(on:/cache:). A root is a lazy\n// shell when it carries any of these; real content carries none.\nfunction deferNeeded(root) {\n return [\"defer-token\", \"defer-src\", \"lazy-on\", \"lazy-visible\"].some(\n (marker) => root.getAttribute?.(`data-reactive-${marker}`) != null,\n )\n}\n\n// Issue #410: a tokenless CLIENT-ONLY root (a ClientBindings component: filter,\n// show, on_client) has no action of its own. It is transparent to server round\n// trips: a server trigger inside it dispatches on the nearest token-bearing root\n// above, and that root's #collectFields looks through it. A lazy or deferred\n// shell is tokenless by design and is NOT client-only.\nconst AWAKE_ROOT = '[data-controller~=\"reactive\"]'\nconst ANY_ROOT = '[data-controller~=\"reactive\"],[data-reactive-dormant~=\"reactive\"]'\n\nconst clientOnlyRoot = (root) => root.getAttribute?.(\"data-reactive-token-value\") == null && !deferNeeded(root)\n\n// The first `selector` root at or above `from` that is not client-only, or\n// `stop` when the walk reaches it first, or null.\nfunction serverRoot(from, selector, stop) {\n let root = from?.closest?.(selector)\n while (root && root !== stop && clientOnlyRoot(root)) root = root.parentElement?.closest?.(selector)\n return root ?? null\n}\n\n// Connected controllers by root element, so a client-only root can hand a\n// dispatch to the server root above it. A dispatch for a root that has not\n// connected yet (a dormant root being woken) waits here until it does.\nconst rootControllers = new WeakMap()\nconst heldForwards = new WeakMap()\n\n// Dirty tracking with its navigate-away guard, and the paste-trigger gate:\n// the root or anything inside it carries one of the two markers. (A nested\n// root's marker loads the module for its parent too; the module then checks\n// ownership before it wires anything.)\nfunction formNeeded(root) {\n return (\n (root.getAttribute?.(\"data-action\") ?? \"\").includes(\"reactive#trackDirty\") ||\n root.getAttribute?.(\"data-reactive-clipboard\") != null ||\n root.querySelector?.('[data-action*=\"reactive#trackDirty\"],[data-reactive-clipboard]') != null\n )\n}\n\n// Show bindings and cross-root show targets, completion bindings, option\n// filtering, the bulk-selection group bindings (enable, select-all, count —\n// issue #319) and their cross-root targets (#343), the tag-chip input, draft\n// nested rows, editable text's keys and paste (#351) and media previews\n// (#373): a form's client-only bindings. The root declares one, or\n// owns an element that does. (The probes the connect-time gates ran before\n// the split, unchanged.)\nfunction bindingsNeeded(root) {\n return (\n [\"show-targets\", \"group-targets\", \"on-complete\", \"filter-input\", \"tags-field\"].some(\n (marker) => root.getAttribute?.(`data-reactive-${marker}`) != null,\n ) ||\n (root.querySelectorAll?.(\"[data-reactive-show-field], [data-reactive-show]\") ?? []).length > 0 ||\n root.querySelector?.(\n \"[data-reactive-nested-json], [data-reactive-nested-list], [data-reactive-confirm-when-param], [data-reactive-enable], [data-reactive-select-all], [data-reactive-count], [data-reactive-editable], [data-reactive-media]\",\n ) != null\n )\n}\n\n// Client-side computes: the root carries a reactive_compute binding.\nfunction computeNeeded(root) {\n return root.getAttribute?.(\"data-reactive-compute-inputs-param\") != null\n}\n\n// Sortable lists (issue #350): the root is, or holds, a sortable list or a\n// source list. (A nested root's list loads the module for its parent too; the\n// module then checks ownership before it picks a row up.)\nfunction sortableNeeded(root) {\n const lists = \"[data-reactive-sortable],[data-reactive-sortable-source]\"\n return Boolean(root.matches?.(lists) || root.querySelector?.(lists))\n}\n\n// Presence (issue #358): the root is a reactive_presence roster.\nfunction presenceNeeded(root) {\n return root.getAttribute?.(\"data-reactive-presence\") != null\n}\n\n// Countdowns (issue #400): the root is, or holds, a reactive_countdown.\nfunction countdownNeeded(root) {\n return Boolean(root.matches?.(\"[data-reactive-countdown]\") || root.querySelector?.(\"[data-reactive-countdown]\"))\n}\n\nconst PRODUCTION_FEATURES = [\n // (Its `waiting` hook is the opt-in entry's: core.js.) The draft restore\n // writes what a request reads, so it gates.\n [\"persist\", [persistNeeded, null, null, true]],\n [\"defer\", [deferNeeded, null]],\n [\"form\", [formNeeded, null]],\n // Its seeds write what a request reads (the JSON-mode rows), so it gates.\n [\"bindings\", [bindingsNeeded, null, null, true]],\n // The seed writes output fields a request collects, so it gates.\n [\"compute\", [computeNeeded, null, null, true]],\n // Nothing it does at connect is read by a request: it never gates.\n [\"sortable\", [sortableNeeded, null]],\n // Its announce is a request of its own; nothing it does at connect is read\n // by another request: it never gates.\n [\"presence\", [presenceNeeded, null]],\n // A countdown writes only its own text and visibility: it never gates.\n [\"countdown\", [countdownNeeded, null]],\n [\n // Stream effects and dismissing flashes are document-level: the module\n // has no connect. A root that DECLARES an effect only gets it fetched\n // early, so the first stream need not wait for it (see streamHold).\n \"effects\",\n [\n (root) => [\"enter\", \"update\", \"exit\"].some((hook) => root.getAttribute?.(`data-reactive-effect-${hook}`) != null),\n null,\n ],\n ],\n [\n // The optimistic/busy hint engine: a trigger in the root declares a hint.\n // No connect — the module acts at enqueue — so the marker is only read\n // while it is not loaded, and a root that declares one preloads it.\n \"hints\",\n [\n (root) =>\n root.querySelector?.(\n \"[data-reactive-optimistic-param], [data-reactive-busy-param], [data-reactive-loading-param]\",\n ) != null,\n null,\n ],\n ],\n [\n // Development aids: the latency simulator (page-level, see\n // registerReactiveDev), the zero-target diagnostics and the debug trace.\n // A root in debug mode preloads it; a verbose root asks for it the first\n // time it has something to warn about.\n \"devtools\",\n [(root) => root.getAttribute?.(\"data-reactive-debug\") === \"true\", null],\n ],\n]\n// (Only the opt-in entry has a table: `new Map` would keep it in the default.)\nconst FEATURES = __SPLIT__ ? new Map(PRODUCTION_FEATURES) : null\n\n// name -> the one import promise every root shares, kept even when it\n// rejected: a browser caches a module that failed to load, so importing it\n// again only re-rejects. A failed feature stays failed until the page is\n// reloaded; every root it costs is told (reactive:error).\nconst featureLoads = __SPLIT__ ? new Map() : null\n// name -> the loaded module: what lets a later root connect a feature inside\n// connect(), and a stream action or client op use it in the same tick.\nconst featureModules = __SPLIT__ ? new Map() : null\n// The modules the default bundle handed over (registerReactiveFeature); only\n// the test seam below reads it back.\nconst featuresGiven = __SPLIT__ ? new Map() : null\n\n// How the opt-in entry (core.js) tells the runtime where a feature module is\n// — `load`, a function returning its import() — and, optionally, what to\n// record while that import is on its way (`waiting`, see above).\nexport function registerReactiveFeatureLoader(name, load, waiting) {\n if (__SPLIT__) {\n featureLoaders.set(name, [load, waiting])\n applyFeatureLoader(name)\n }\n}\n\n// name -> [load, waiting] the opt-in entry registered: kept apart from the\n// table so a test's reset of the table puts them back.\nconst featureLoaders = __SPLIT__ ? new Map() : null\n\nfunction applyFeatureLoader(name) {\n const entry = FEATURES.get(name)\n const [load, waiting] = featureLoaders.get(name)\n entry[1] = load\n entry[2] = waiting\n}\n\n// Hand the runtime a feature that is ALREADY loaded — what\n// phlex/reactive/reactive_controller does for every feature before anything\n// can connect. Such a feature is never imported: it connects inside connect().\nexport function registerReactiveFeature(name, feature) {\n if (__SPLIT__) {\n feature.install?.(FEATURE_SHARED)\n featureModules.set(name, feature)\n featuresGiven.set(name, feature)\n }\n}\n\n// For the opt-in entry: the features some of `elements` (reactive roots a\n// stream is about to render) need and that are not loaded — read off the\n// table, so a new feature is covered by its own marker check.\nexport function unloadedFeaturesFor(elements) {\n const names = []\n if (__SPLIT__) {\n for (const [name, [needs]] of FEATURES) {\n if (!needs || featureModules.has(name)) continue\n try {\n if (elements.some((el) => needs(el))) names.push(name)\n } catch {\n // a marker check that throws is that root's problem at connect, not the stream's\n }\n }\n }\n return names\n}\n\n// For the opt-in entry: import a feature now. Resolves with the module, or\n// with undefined once the failure has been logged.\nexport function loadReactiveFeature(name) {\n return __SPLIT__\n ? loadFeature(name).catch((error) => logFeatureFailure(name, \"load\", error))\n : Promise.resolve()\n}\n\nfunction loadFeature(name) {\n let loading = featureLoads.get(name)\n if (!loading) {\n const load = FEATURES.get(name)[1] ?? (() => Promise.reject(new Error(\"this entry has no way to import it\")))\n loading = load().then((feature) => {\n feature.install?.(FEATURE_SHARED)\n featureModules.set(name, feature)\n return feature\n })\n featureLoads.set(name, loading)\n }\n return loading\n}\n\n// Logged once per feature and phase per page (a failed import would otherwise\n// log again for every root that connects). `phase` says what went wrong:\n// \"detect\" (its marker check threw), \"load\" (the import failed — and stays\n// failed until the page is reloaded), \"timeout\" (slow; the root carried on\n// without it) or \"connect\" (it threw while wiring a root).\nconst featureFailuresLogged = new Set()\n\nfunction logFeatureFailure(name, phase, error) {\n const logged = name + phase\n if (featureFailuresLogged.has(logged)) return\n featureFailuresLogged.add(logged)\n console.error(\n `[phlex-reactive] the \"${name}\" feature module (phlex/reactive/features/${name}) is unavailable: ${phase}. ` +\n \"If you bundle or vendor the client, alias or pin phlex/reactive/features/* (README: esbuild / webpack / bun).\",\n error,\n )\n}\n\n// Run `use(feature)` with a feature — for code with no root connection to\n// wait on (a client op, a stream action): in this tick when the module is\n// loaded, else once it has arrived. A feature that cannot load is logged.\nfunction withFeature(name, use) {\n const feature = featureModules.get(name)\n if (feature) return use(feature)\n loadFeature(name).then(use, (error) => logFeatureFailure(name, \"load\", error))\n}\n\n// How long a root waits for a feature import before it carries on without it\n// (<meta name=\"phlex-reactive-feature-timeout\" content=\"ms\"> in <head>).\n// 10 s: the modules are a few KB, so even a slow 3G fetch (seconds, not tens\n// of seconds) finishes well inside it, and it matches the early-event TTL —\n// the other \"how long may the client be late\" window. A feature that arrives\n// AFTER the timeout still connects; the timeout only stops it holding back\n// the root's other features and its action requests.\nconst DEFAULT_FEATURE_TIMEOUT_MS = 10000\n\nfunction featureTimeoutMs() {\n const ms = Number(globalThis.document?.head?.querySelector?.('meta[name=\"phlex-reactive-feature-timeout\"]')?.content)\n return Number.isFinite(ms) && ms > 0 ? ms : DEFAULT_FEATURE_TIMEOUT_MS\n}\n\nexport function reactiveFeatureNames() {\n return __SPLIT__\n ? [...FEATURES.keys()]\n : [\"persist\", \"defer\", \"form\", \"bindings\", \"compute\", \"sortable\", \"presence\", \"countdown\", \"effects\", \"hints\", \"devtools\"]\n}\n\n\n// Test-only: a feature's table entry, to wrap one part of it.\n\n// Test-only: import a feature now (a later connect is then synchronous).\n\n// Test-only: back to the shipped table, with nothing logged and only the\n// modules the default bundle handed over still loaded — or, `cold`, with none\n// loaded at all: the state of a page that imported phlex/reactive/core.\n\nconst FEATURES_READY = Promise.resolve()\n\n// The single-instance module state a feature may add to (install()): ONE\n// in-flight activity count and one request total per page, whoever counts.\n// `waiting` maps a root to { <feature name>: pending } while it waits for that\n// feature's import (see the `waiting` hook above).\nfunction forgetWaiting(root, name, pending) {\n const waiting = FEATURE_SHARED.waiting.get(root)\n // (A root that reconnected meanwhile has a NEW record under the same name.)\n if (waiting && waiting[name] === pending) delete waiting[name]\n}\n\nconst FEATURE_SHARED = {\n enter: enterReactiveActivity,\n exit: exitReactiveActivity,\n count: countReactiveRequest,\n // The editable collector and server text (issue #351), for the draft.\n text: editableValue,\n served: serverText,\n waiting: new WeakMap(),\n}\n\n// The default entry's per-root features, one bit each (#connectFeaturesNow).\nconst PERSIST = 1\nconst DEFER = 2\nconst FORM = 4\nconst BINDINGS = 8\nconst COMPUTE = 16\nconst SORTABLE = 32\nconst PRESENCE = 64\nconst COUNTDOWN = 128\n\n// The default entry's features are part of this file: install them now, as\n// the opt-in entry does when one arrives.\nif (!__SPLIT__) {\n persistFeature.install(FEATURE_SHARED)\n deferFeature.install(FEATURE_SHARED)\n}\n\n// --- Early triggers (issue #273) ----------------------------------------------\n// phlex/reactive/early (imported eagerly by the app) queues trigger events that\n// reach a root before its controller connects. The queue is shared through a\n// Symbol.for key on window — no import edge either way, so either module may\n// load first, and an app that never imports early gets an empty queue.\nconst EARLY_KEY = Symbol.for(\"phlex-reactive.early\")\nconst DEFAULT_EARLY_TTL_MS = 10000\n// A window-bound trigger (a hotkey, issue #303) is replayed only this soon\n// after the keypress: later, the user has moved on. A shorter configured TTL\n// still wins.\nconst WINDOW_EARLY_TTL_MS = 1500\n\n// Is this event's currentTarget a Window (a Stimulus `@window` listener)? The\n// page's window, or any Window recognised structurally (another document's,\n// which a replay built from its trigger element's document carries).\nconst isWindow = (target) => target != null && (target === globalThis.window || target.window === target)\n\n// Issue #393: the key on()'s dispatch debounce uses in #debounceTimers. An\n// on_client record keys on its JSON, which starts with \"{\", so never this.\nconst DISPATCH_WAIT_KEY = \"dispatch\"\n\nfunction earlyState() {\n globalThis[EARLY_KEY] ??= { queue: [], connected: new WeakSet() }\n return globalThis[EARLY_KEY]\n}\n\n// <meta name=\"phlex-reactive-early-ttl\" content=\"ms\"> (Phlex::Reactive.\n// early_event_ttl_ms), parsed defensively like the timeout meta.\nfunction earlyTtlMs() {\n const ms = Number(globalThis.document?.querySelector?.('meta[name=\"phlex-reactive-early-ttl\"]')?.content)\n return Number.isFinite(ms) && ms > 0 ? ms : DEFAULT_EARLY_TTL_MS\n}\n\n// Stimulus's param reader (data-reactive-<name>-param, JSON-typecast,\n// camelized) — a replayed trigger carries the same event.params a live one does.\nfunction stimulusParams(el) {\n const params = {}\n for (const { name, value } of Array.from(el.attributes ?? [])) {\n const match = /^data-reactive-(.+)-param$/i.exec(name)\n if (!match) continue\n let typed\n try {\n typed = JSON.parse(value)\n } catch {\n typed = value\n }\n params[match[1].replace(/[-_]([a-z0-9])/g, (_, char) => char.toUpperCase())] = typed\n }\n return params\n}\n\n// The event a replay hands to dispatch()/runOps(). It is called DIRECTLY, not\n// re-dispatched on the element: a real event would also re-run every OTHER\n// listener on the trigger (a lazily connected sibling controller that already\n// handled the original click would toggle twice). The original's default was\n// already prevented (or deliberately kept) by early.js, so preventDefault here\n// is a no-op. A window-bound entry (issue #303) is replayed as Stimulus's\n// window listener sees it: currentTarget is the window, params come from `el`.\nfunction earlyReplayEvent(event, el, win) {\n return {\n [EARLY_KEY]: true,\n type: event.type,\n detail: event.detail,\n target: event.target,\n currentTarget: win ? el.ownerDocument.defaultView : el,\n params: stimulusParams(el),\n key: event.key,\n code: event.code,\n metaKey: event.metaKey,\n ctrlKey: event.ctrlKey,\n altKey: event.altKey,\n shiftKey: event.shiftKey,\n button: event.button,\n submitter: event.submitter,\n bubbles: event.bubbles,\n timeStamp: event.timeStamp,\n defaultPrevented: event.defaultPrevented,\n preventDefault() {},\n stopPropagation() {},\n stopImmediatePropagation() {},\n }\n}\n\n// A :once trigger that was REPLAYED is spent, but Stimulus's own `once`\n// listener for it is still armed (the replay calls the method directly). The\n// markup is left alone — rewriting data-action would let a morph, which writes\n// the server's attribute back, re-arm it — so the spent descriptor is tracked\n// per trigger element (a WeakMap: a replaced element starts fresh, like a live\n// `once`), and the armed listener's single firing is swallowed. A window-bound\n// descriptor's live call carries the window as currentTarget, not its\n// element, so it is tracked under the ROOT instead (issue #303), flagged\n// `win` (apart from the root's own element-bound descriptors) and told apart\n// from its siblings by the params Stimulus reads off its element.\n//\n// Known limit of the root-keyed entry (the same class as onceBindingSpent's\n// window-bound limit above): a reply that MORPHS the root but swaps the\n// trigger element gives Stimulus a fresh `once` listener for the new element\n// while this entry stays armed, so the next live press is swallowed here and\n// that fresh listener is consumed — the hotkey is dead until the root\n// disconnects. Keep a `:once` hotkey's trigger element stable across replies,\n// or bind the hotkey without `once:`.\nconst spentEarlyOnce = new WeakMap()\n\nfunction spendEarlyOnce(owner, { token, type, method, filter, win }, params) {\n let spent = spentEarlyOnce.get(owner)\n if (!spent) spentEarlyOnce.set(owner, (spent = new Map()))\n const key = win ? `${token} ${params}` : token\n if (spent.has(key)) return false\n spent.set(key, { type, method, filter, win, params, armed: true })\n return true\n}\n\n// True (once) for the live call that comes from a spent descriptor's listener:\n// same element, method, event type AND key filter — a sibling descriptor with\n// another filter (keydown.esc beside a spent keydown.enter:once) is not it.\n//\n// Also true for the ORIGINAL of a replayed event arriving live at the same\n// element (issue #274): waking a dormant root can connect its controller while\n// the waking event is still propagating (an eagerly registered controller\n// connects in the microtask checkpoint after early.js's capture listener), so\n// the event reaches the listener Stimulus just bound after it was replayed.\n// Only as many live calls as the replay RAN for that element and method are\n// dropped — a binding the replay skipped (a key filter only the app's own\n// mapping matches) still runs.\nfunction earlyOnceSwallows(event, method, keyMappings, root) {\n if (event?.[EARLY_KEY]) return false\n const win = isWindow(event?.currentTarget)\n // A window-bound replay is owned by its ROOT: another root's binding on the\n // same window event is not it (issue #303).\n const owner = win ? root : event?.currentTarget\n const replayed = takeEarlyReplay(event, owner, win ? `@${method}` : method)\n const spent = spentEarlyOnce.get(owner)\n const params = win && JSON.stringify(event.params)\n for (const entry of spent?.values() ?? []) {\n if (!entry.armed || entry.method !== method || entry.type !== event.type || Boolean(entry.win) !== win) continue\n if (win && entry.params !== params) continue\n if (entry.filter && !keyFilterMatches(entry.filter, event, keyMappings)) continue\n entry.armed = false\n return true\n }\n return replayed\n}\n\n// event → (element → { dispatch: n, runOps: n }): how many bindings a replay\n// ran while its original may still be propagating. The entry lasts for that\n// propagation only — a new task starts after the whole dispatch is over — so\n// the same event OBJECT dispatched again later is live.\nconst replayedEarly = new WeakMap()\n\nfunction markEarlyReplay(event, el, method) {\n let byElement = replayedEarly.get(event)\n if (!byElement) {\n replayedEarly.set(event, (byElement = new Map()))\n setTimeout(() => replayedEarly.delete(event))\n }\n const counts = byElement.get(el) ?? {}\n byElement.set(el, counts)\n counts[method] = (counts[method] ?? 0) + 1\n}\n\nfunction takeEarlyReplay(event, owner, method) {\n const counts = event && replayedEarly.get(event)?.get(owner)\n if (!counts?.[method]) return false\n counts[method] -= 1\n return true\n}\n\n// Register this controller eagerly OR lazily: with phlex/reactive/early\n// imported, a trigger that fires before connect is replayed on connect (issue\n// #273). The engine auto-pins it with preload: true for importmap apps; see\n// the README for esbuild/webpack.\nexport default class extends Controller {\n static values = {\n token: String, // signed identity token (component + record gid/state)\n }\n\n #tokenCache // freshest token, threaded synchronously across queued requests\n #debounceTimers = new Map() // trigger element -> Map(key -> { timer, flush }) pending run\n #throttleTimers = new Map() // trigger element -> Map(action or record key -> suppression timer)\n #actionPathCache // page-stable action path, resolved once per controller\n #timeoutMsCache // page-stable request timeout (ms), resolved once per controller (issue #101)\n #tokenRegexCache // { id, token, self } — #extractToken's two per-id RegExps, rebuilt on id change (issue #118)\n // Loading-state bookkeeping (issue #99). All keyed so overlapping enqueues\n // refcount correctly and never clobber each other:\n #busyPending = 0 // root aria-busy pending counter (remove only at zero)\n #busyActions = new Map() // action -> in-flight count (root's space-separated busy set + busy_on)\n #busyTokenCounts = new WeakMap() // element -> Map(action -> count): its data-reactive-busy token set\n // Dirty tracking (issue #103): the bound re-scan (turbo:morph-element) and the\n // navigate-away guard handlers, held so disconnect() can remove exactly them.\n // The root-only morph listener of a token root or lazy shell (#275, #312): a morph\n // can add a feature's marker to a connected root. Held for teardown.\n #boundRootMorph\n // Clipboard-trigger availability gate (issue #228): the bound morph re-sync,\n // held for teardown.\n // Client-only drafts (issue #239): the parsed root payload (null when\n // undeclared), the restore-complete latch (no write may run before the\n // connect restore — a connect must never overwrite a draft with server\n // blanks), the ONE per-root trailing-edge write timer, and the bound\n // input/change/turbo:submit-end handlers held for teardown.\n // Feature modules (issue #275). `featuresReady` resolves once the features\n // this root needs have connected (or been given up on: failed, or slower\n // than the timeout) — it never rejects, and is already resolved for a root\n // that needs none. connect() only STARTS the imports. The name is long on\n // purpose: it is a public field, and a short `ready` would shadow a method\n // an app's own subclass may well define.\n // #featureEpoch changes on every connect and disconnect, so an import that\n // resolves for a connection that has since ended connects nothing.\n featuresReady = FEATURES_READY\n #features = new Map() // name -> module connected on this connection\n #featuresWanted = new Set() // names this connection has asked for\n #featuresSettling = false // true while featuresReady is still pending\n #featureGate = null // settles when the last feature that `gates` has connected\n #featureWaits = new Map() // pending import-timeout timer -> its wait's resolve\n #featureHooks = new Map() // name -> [undo, pending] of a feature still on its way\n #featureTurns = new Map() // name -> settles once that feature has had its turn to connect\n #featureEpoch = 0\n #featureHandle // the `core` handle features receive, built on first use\n // The default entry (issue #305): one bit per feature, PERSIST … COUNTDOWN —\n // asked for (its marker read true) and connected on this connection.\n #featuresAsked = 0\n #featuresOn = 0\n\n // Mark that a reactive controller actually connected, so the registration\n // guard above knows the controller was registered (issue #26 part 2).\n connect() {\n reactiveConnected = true\n // Early triggers (issue #273): from here on Stimulus delivers this root's\n // events live (its bindings are already wired), so early.js must stop\n // queueing them NOW — a connect-time seed below (a compute output write)\n // dispatches real input events. The queue itself drains at the very end.\n earlyState().connected.add(this.element)\n rootControllers.set(this.element, this)\n\n // Root-id guard (issue #48). The token round trip assumes the reactive root\n // element's id == component.id: the server targets component.id and the client\n // self-matches its NEXT token by this.element.id (#extractToken, issue #46).\n // If `id:` landed on a CHILD instead of the `**reactive_attrs` root, this id is\n // \"\" — #extractToken falls back to the FIRST token in the response (a child's),\n // so the next action POSTs a foreign token → endpoint default-deny → silent 403.\n // Warn NOW (on connect) so the failure surfaces on page load, not on click 2.\n if (this.element.id === \"\") {\n console.warn(\n \"[phlex-reactive] a reactive root has no id; its next-action token can't self-match \" +\n \"and may fall back to the first token in the response → a silent HTTP 403 on the NEXT action. \" +\n \"Put id: on the SAME element as reactive_attrs — use div(**reactive_root) (emits id + token together), \" +\n \"or div(id:, **reactive_attrs). The id: must NOT be on a child. See the README.\"\n )\n }\n\n // Lazy shells (reactive_lazy, on:, cache:) are wired by the defer feature\n // module (issue #275), which #loadFeatures below starts importing when the\n // root carries one of its markers. What stays here is the root-only morph\n // listener of a TOKEN-BEARING root or a LAZY SHELL: a Turbo morph can turn\n // a root that connected on REAL content (no marker, so no feature) into a\n // shell while it stays connected — no Stimulus lifecycle fires — and only\n // a listener that is already there can notice and load the feature. A\n // shell carries no identity token (a `cache:` one has its URL, issue #306;\n // a plain one its defer token), and a morph can turn it, still connected,\n // into a root that needs another feature (issue #312). It costs a root\n // without any marker, before and after the morph, a few attribute reads and\n // never an import. (turbo:morph-element BUBBLES: only a morph of the root\n // itself counts.) A tokenless, client-only root has no such listener.\n // The same listener hears an editable being focused (issue #351): a stream\n // can append a root's first reactive_editable without morphing the root,\n // and its keys and paste need the bindings feature before anything is\n // typed into it. And it hears a stream that rendered into the root\n // (STREAMED, issue #356): the features already connected re-run their\n // seeds first (a morph's own listeners do that for a morph), then what the\n // new content needs connects.\n if (this.element.getAttribute?.(\"data-reactive-token-value\") != null || deferNeeded(this.element)) {\n this.#boundRootMorph = (event) => {\n if (event.type === \"focusin\" ? !event.target.hasAttribute?.(\"data-reactive-editable\") : event.target !== this.element) return\n if (event.type === STREAMED) this.#reseed()\n if (__SPLIT__) this.#loadFeatures(true)\n else this.#connectFeaturesNow(true)\n }\n this.element.addEventListener?.(\"turbo:morph-element\", this.#boundRootMorph)\n this.element.addEventListener?.(\"focusin\", this.#boundRootMorph)\n this.element.addEventListener?.(STREAMED, this.#boundRootMorph)\n }\n\n // Feature modules (issue #275): connect the ones that are here and START\n // the import of any that is not. HERE, before the seeds below, because\n // this is where their work used to sit: the draft restore (persist) writes\n // the values those seeds read, the dirty baseline (form) comes before\n // them. A module that is still on its way connects when it arrives and\n // re-runs the seeds itself (#reseed). Never awaited — connect() must reach\n // the drain at the end in the same task (issue #274: an await before it\n // runs a waking click twice).\n // The editables' server texts (issue #351), before a draft restore\n // writes into them.\n this.element.querySelectorAll?.(\"[data-reactive-editable]\").forEach(seedServerText)\n\n this.#featureEpoch++\n this.featuresReady = FEATURES_READY\n if (__SPLIT__) this.#loadFeatures()\n else this.#connectFeaturesNow()\n\n // A dialog the server rendered open with data-reactive-modal (issue #365).\n upgradeModals(this.element)\n\n // LAST, after every feature above is wired: a replayed trigger must find\n // the controller exactly as a live event after connect would.\n this.#announceConnected()\n }\n\n // The default entry (issue #305): every feature is in this file, so each\n // one this root's markers ask for connects now, by a direct call, in the\n // order the opt-in entry's table gives (persist first: its restore writes\n // the values the later seeds read). Again after a morph of the root, for a\n // marker the morph added. The features with no per-root connect (effects,\n // hints, devtools) act when called.\n #connectFeaturesNow(morphed) {\n if (!__SPLIT__) {\n this.#connectFeatureNow(PERSIST, \"persist\", persistNeeded, (core) => persistFeature.connect(this, core, morphed))\n this.#connectFeatureNow(DEFER, \"defer\", deferNeeded, (core) => deferFeature.connect(this, core, morphed))\n this.#connectFeatureNow(FORM, \"form\", formNeeded, (core) => formFeature.connect(this, core, morphed))\n this.#connectFeatureNow(BINDINGS, \"bindings\", bindingsNeeded, (core) => bindingsFeature.connect(this, core, morphed))\n this.#connectFeatureNow(COMPUTE, \"compute\", computeNeeded, (core) => computeFeature.connect(this, core, morphed))\n this.#connectFeatureNow(SORTABLE, \"sortable\", sortableNeeded, (core) => sortableFeature.connect(this, core))\n this.#connectFeatureNow(PRESENCE, \"presence\", presenceNeeded, (core) => presenceFeature.connect(this, core))\n this.#connectFeatureNow(COUNTDOWN, \"countdown\", countdownNeeded, (core) => countdownFeature.connect(this, core))\n }\n }\n\n // Once per connection: a marker check that throws loses that feature (and\n // is read again after a morph), a connect that throws loses it until the\n // root reconnects — never the rest of connect().\n #connectFeatureNow(bit, name, needs, connect) {\n if (!__SPLIT__) {\n if (this.#featuresAsked & bit) return\n try {\n if (!needs(this.element)) return\n } catch (error) {\n return this.#featureFailed(name, \"detect\", error)\n }\n this.#featuresAsked |= bit\n try {\n connect(this.#featureCore())\n this.#featuresOn |= bit\n } catch (error) {\n this.#featureFailed(name, \"connect\", error)\n }\n }\n }\n\n // FIRST in disconnect(), in connect order, while the root is still intact\n // (the draft flush reads its fields).\n #disconnectFeaturesNow() {\n if (!__SPLIT__) {\n const on = this.#featuresOn\n this.#featuresAsked = this.#featuresOn = 0\n const core = this.#featureCore()\n if (on & PERSIST) this.#disconnectFeatureNow(\"persist\", () => persistFeature.disconnect(this, core))\n if (on & DEFER) this.#disconnectFeatureNow(\"defer\", () => deferFeature.disconnect(this, core))\n if (on & FORM) this.#disconnectFeatureNow(\"form\", () => formFeature.disconnect(this, core))\n if (on & BINDINGS) this.#disconnectFeatureNow(\"bindings\", () => bindingsFeature.disconnect(this, core))\n if (on & COMPUTE) this.#disconnectFeatureNow(\"compute\", () => computeFeature.disconnect(this, core))\n if (on & SORTABLE) this.#disconnectFeatureNow(\"sortable\", () => sortableFeature.disconnect(this, core))\n if (on & PRESENCE) this.#disconnectFeatureNow(\"presence\", () => presenceFeature.disconnect(this, core))\n if (on & COUNTDOWN) this.#disconnectFeatureNow(\"countdown\", () => countdownFeature.disconnect(this, core))\n }\n }\n\n #disconnectFeatureNow(name, disconnect) {\n if (!__SPLIT__) {\n try {\n disconnect()\n } catch (error) {\n console.error(`[phlex-reactive] the \"${name}\" feature module failed to disconnect`, error)\n }\n }\n }\n\n // Starts the import of every feature this root needs and has not asked for\n // yet — at connect, and again after a morph of the root (which may have\n // added a marker; `morphed`). Every import starts now; each feature connects\n // when its own module has arrived (or is given up on) AND the features\n // before it in the table have had their turn — the order their connect-time\n // seeds depend on, never the order the network delivered them in — unless\n // this connection ended meanwhile.\n #loadFeatures(morphed) {\n if (__SPLIT__) {\n const epoch = this.#featureEpoch\n const names = []\n for (const [name, [needs]] of FEATURES) {\n if (!needs || this.#featuresWanted.has(name)) continue\n // Loaded, and with nothing to do per root: its marker need not be read.\n const loaded = featureModules.get(name)\n if (loaded && !loaded.connect) continue\n // A marker check that throws loses that feature, never the rest of\n // connect() — above all not the early drain that follows it.\n try {\n if (needs(this.element)) names.push(name)\n } catch (error) {\n this.#featureFailed(name, \"detect\", error)\n }\n }\n if (names.length === 0) return\n for (const name of names) this.#featuresWanted.add(name)\n // Every module already here (a later root, the next Turbo visit): connect\n // now, in this task — nothing to wait for, so nothing to gate or time out.\n if (!this.#featuresSettling && names.every((name) => featureModules.has(name))) {\n for (const name of names) this.#connectFeature(epoch, name, featureModules.get(name), morphed)\n return\n }\n for (const name of names) {\n const arrival = this.#awaitFeature(epoch, name)\n // Its turn comes after the features BEFORE it in the table that this\n // root is still waiting for — never after a later one, even one whose\n // import started earlier (a morph can add an earlier feature's marker).\n const earlier = []\n for (const key of FEATURES.keys()) {\n if (key === name) break\n if (this.#featureTurns.has(key)) earlier.push(this.#featureTurns.get(key))\n }\n const turn = Promise.all(earlier)\n .then(() => arrival)\n .then((feature) => feature && this.#connectFeature(epoch, name, feature, morphed))\n this.#featureTurns.set(name, turn)\n if (FEATURES.get(name)[3]) this.#featureGate = turn\n }\n // Requests stop waiting the moment the last gating feature has connected.\n const gate = this.#featureGate\n gate?.then(() => {\n if (this.#featureGate === gate) this.#featureGate = null\n })\n this.#featuresSettling = true\n const ready = Promise.all(this.#featureTurns.values()).then(() => {\n if (this.featuresReady === ready) this.#featuresSettling = false\n })\n this.featuresReady = ready\n }\n }\n\n // One feature's import, for one scan: resolves with the module, or with\n // null once the import failed or outlasted the timeout — so one slow or\n // broken feature never holds back the others. A module that arrives after\n // its timeout still connects (late, and so out of table order).\n #awaitFeature(epoch, name) {\n if (__SPLIT__) {\n const current = () => epoch === this.#featureEpoch\n this.#startWaitingFor(name)\n return new Promise((resolve) => {\n let timedOut = false\n const timer = setTimeout(() => {\n this.#featureWaits.delete(timer)\n timedOut = true\n if (current()) this.#featureFailed(name, \"timeout\", new Error(`not loaded after ${featureTimeoutMs()} ms`))\n resolve(null)\n }, featureTimeoutMs())\n // Kept with its resolve: a disconnect ends the wait (#disconnectFeatures),\n // or whatever is queued behind featuresReady would hang on a stuck import.\n this.#featureWaits.set(timer, resolve)\n const settled = () => {\n clearTimeout(timer)\n this.#featureWaits.delete(timer)\n }\n loadFeature(name).then(\n (feature) => {\n settled()\n // Late: connect one microtask on, after whatever else was queued on\n // this import while the root waited.\n if (timedOut) queueMicrotask(() => this.#connectFeature(epoch, name, feature))\n else resolve(feature)\n },\n (error) => {\n settled()\n if (current()) this.#featureFailed(name, \"load\", error)\n resolve(null)\n },\n )\n })\n }\n }\n\n // Run the feature's `waiting` hook (if it has one) and remember its undo and\n // what it records, for #connectFeature or #disconnectFeatures to hand on.\n #startWaitingFor(name) {\n if (__SPLIT__) {\n const hook = FEATURES.get(name)[2]\n if (!hook) return\n const pending = {}\n try {\n this.#featureHooks.set(name, [hook(this.element, pending), pending])\n FEATURE_SHARED.waiting.set(this.element, { ...FEATURE_SHARED.waiting.get(this.element), [name]: pending })\n } catch (error) {\n this.#featureFailed(name, \"detect\", error)\n }\n }\n }\n\n // The wait is over (the feature is here, or the root is leaving): undo the\n // hook and return what it recorded. The shared record goes with it unless\n // `abandoning`: a root that leaves keeps its record until abandon() has run,\n // so anything else queued on the same import still finds it.\n #stopWaitingFor(name, abandoning) {\n if (__SPLIT__) {\n const [undo, pending] = this.#featureHooks.get(name) ?? []\n this.#featureHooks.delete(name)\n if (!abandoning) forgetWaiting(this.element, name, pending)\n try {\n undo?.()\n } catch (error) {\n console.error(`[phlex-reactive] the \"${name}\" feature module failed to stop waiting`, error)\n }\n return pending\n }\n }\n\n #connectFeature(epoch, name, feature, morphed) {\n if (__SPLIT__) {\n if (epoch !== this.#featureEpoch) return\n const pending = this.#stopWaitingFor(name)\n try {\n feature.connect?.(this, this.#featureCore(), morphed, pending)\n this.#features.set(name, feature)\n } catch (error) {\n this.#featureFailed(name, \"connect\", error)\n }\n }\n }\n\n // The `core` handle a feature receives (see \"Feature modules\" above).\n #featureCore() {\n this.#featureHandle ??= {\n emit: (name, detail, options) => this.#emit(name, detail, options),\n reseed: () => this.#reseed(),\n // Resolves with the reply's HTML once the request has applied, or with\n // nothing when it failed or was vetoed. `busy` is on(busy:)'s hint.\n proceed: (target, action, params, busy) => this.#proceed(target, action, params, 0, 0, null, busy),\n forgetToken: () => {\n this.#tokenCache = undefined\n },\n owns: (el) => this.#ownsField(el),\n ownership: () => this.#ownershipFilter(),\n opTargets: (args) => this.#opTargets(args),\n diagnose: (label, args) => this.#diagnoseZeroTargets(label, args),\n // Apply an op list root-scoped, with the zero-target diagnostics; a\n // missing to: defaults to `defaultTo` (the reducer / completion-binding\n // convention is \"@root\").\n applyOps: (list, defaultTo) =>\n applyOps(\n list,\n (args) => this.#opTargets(defaultTo != null && args.to == null ? { ...args, to: defaultTo } : args),\n (name, args) => this.#diagnoseZeroTargets(`client op \"${name}\"`, args),\n ),\n listnavOptions: (event) => this.#listnavOptions(event),\n collectFields: () => this.#collectFields(),\n // A reactive_editable's value and its last server-rendered text (#351),\n // and whether the engine has contenteditable=\"plaintext-only\" (#355).\n plain: plaintextSupported,\n text: editableValue,\n served: serverText,\n // Copy one <img> onto another (issue #373): the reactive_media binding.\n copyMedia,\n // set_value's write (issue #374): reactive_show clear: (issue #394).\n setValue: setFieldValue,\n // The confirm gate, as dispatch() runs it: the resolver inside the chain\n // (a synchronous throw is a cancel), never a rejection out.\n confirm: (message, context) =>\n Promise.resolve()\n .then(() => confirmResolver(message, context))\n .catch(() => false),\n }\n return this.#featureHandle\n }\n\n // Re-run the connect-time seeds that read field values, in connect() order\n // — for a feature that changed those values after connect() ran (the draft\n // restore), or content a stream rendered into the root (issue #356). Each is\n // the same re-sync its feature runs after a morph; a root that did not opt\n // into one skips it. on-complete re-ARMS without firing. Form and bindings\n // also wire a binding whose gate reads true only now (a stream's content).\n #reseed() {\n if (!__SPLIT__) {\n if (this.#featuresOn & FORM) formFeature.reseed(this)\n if (this.#featuresOn & BINDINGS) bindingsFeature.reseed(this)\n if (this.#featuresOn & COMPUTE) computeFeature.seed(this)\n } else {\n this.#features.get(\"form\")?.reseed(this)\n this.#features.get(\"bindings\")?.reseed(this)\n this.#features.get(\"compute\")?.seed(this)\n }\n }\n\n // A feature that is missing leaves its part of the root dead: say so on\n // every root it costs (reactive:error, the error marker), and once in the\n // console. `phase` is \"detect\", \"load\", \"timeout\" or \"connect\".\n #featureFailed(name, phase, error) {\n logFeatureFailure(name, phase, error)\n this.#markError(\"feature\")\n // A lazy shell whose module will never come (a failed import stays failed\n // until the page is reloaded) must not go on shimmering as if it loaded.\n if (phase === \"load\" && this.element.getAttribute?.(\"data-reactive-defer-pending\")) {\n this.element.removeAttribute(\"data-reactive-defer-pending\")\n this.element.removeAttribute(\"aria-busy\")\n }\n this.#emit(\"reactive:error\", { kind: \"feature\", feature: name, phase, error })\n }\n\n // Runs FIRST in disconnect(), in table order, while the root is still\n // intact (the draft flush reads its fields). A feature whose disconnect\n // throws must not keep the others, or the rest of disconnect(), from running.\n #disconnectFeatures() {\n if (__SPLIT__) {\n this.#featureEpoch++\n // End every wait still open: nothing will connect on this connection, and\n // a request queued behind featuresReady must not hang on a stuck import.\n for (const [timer, resolve] of this.#featureWaits) {\n clearTimeout(timer)\n resolve(null)\n }\n this.#featureWaits.clear()\n // A feature this root was still waiting for never connected here: hand it\n // what its hook recorded, once (and if) the module arrives.\n for (const name of [...this.#featureHooks.keys()]) {\n const pending = this.#stopWaitingFor(name, true)\n const root = this.element\n withFeature(name, (feature) => {\n feature.abandon?.(root, pending)\n forgetWaiting(root, name, pending)\n })\n }\n this.#featuresWanted.clear()\n this.#featureTurns.clear()\n this.#featuresSettling = false\n this.#featureGate = null\n const connected = [...this.#features]\n this.#features.clear()\n for (const [name, feature] of connected) {\n try {\n feature.disconnect?.(this, this.#featureCore())\n } catch (error) {\n console.error(`[phlex-reactive] the \"${name}\" feature module failed to disconnect`, error)\n }\n }\n }\n }\n\n // Early triggers (issue #273): mark the root (the attribute is for CSS and\n // tests; early.js trusts the WeakSet joined at the top of connect()),\n // announce it with a bubbling reactive:connect, then replay its queued triggers.\n #announceConnected() {\n // The attribute is a connect-time marker only: an in-place morph writes\n // the server's attributes back and strips it (re-marking would cost every\n // root a morph listener — the \"a root that never opted in pays nothing\"\n // contract). The connected WeakSet is the truth; reactive:connect the signal.\n this.element.setAttribute?.(\"data-reactive-connected\", \"\")\n // Raw dispatch (as #emit), on the root only: connect() runs on an attached\n // element, so there is no detached-node fallback to make.\n if (this.element.isConnected) {\n this.element.dispatchEvent?.(\n new CustomEvent(\"reactive:connect\", { bubbles: true, composed: true, detail: { id: this.element.id } }),\n )\n }\n this.#drainEarly()\n // Issue #410: dispatches a client-only root inside handed over before this\n // root connected (a dormant root woken by that very trigger).\n // currentTarget is null once propagation ended: pin the trigger back on.\n for (const [event, target] of heldForwards.get(this.element) ?? []) {\n if (event.currentTarget !== target) Object.defineProperty(event, \"currentTarget\", { value: target, configurable: true })\n this.dispatch(event)\n }\n heldForwards.delete(this.element)\n }\n\n // The trigger elements whose :once descriptor THIS connection replayed —\n // forgotten on disconnect (see disconnect()).\n #earlySpentOn = new Set()\n\n // Takes this root's entries — and those of any root that left the page\n // before connecting (replaced by a stream), which no controller would ever\n // claim.\n #drainEarly() {\n const { queue } = earlyState()\n if (queue.length === 0) return\n const mine = []\n for (let i = queue.length - 1; i >= 0; i--) {\n const { root } = queue[i]\n if (root === this.element || !root.isConnected) mine.unshift(...queue.splice(i, 1))\n }\n const ttl = earlyTtlMs()\n // Two module instances of early.js (a bundled copy beside the pinned one)\n // each queue the same event: replay an (event, element) pair once. (One\n // event OBJECT dispatched twice on one element before connect also counts\n // once — indistinguishable here.) An element bound to both `click` and\n // `click@window` is heard by both listeners live, so its window-bound\n // entry (issue #303) is a separate pair.\n const replayed = new Map()\n for (const entry of mine) {\n const pair = replayed.get(entry.event) ?? [new Set(), new Set()]\n replayed.set(entry.event, pair)\n const seen = pair[+!!entry.win]\n if (seen.has(entry.el)) continue\n seen.add(entry.el)\n const limit = entry.win ? Math.min(WINDOW_EARLY_TTL_MS, ttl) : ttl\n const reason =\n entry.root !== this.element\n ? \"its root left the page before a controller connected\"\n : performance.now() - entry.at > limit\n ? `it is older than the ${limit} ms ${entry.win ? \"window-trigger\" : \"early-event\"} TTL`\n : entry.el.isConnected && this.element.contains(entry.el)\n ? null\n : \"its element left the root before the controller connected\"\n if (reason) {\n if (this.#verboseEnabled()) console.warn(`[phlex-reactive] dropped an early \"${entry.event.type}\" trigger: ${reason}`)\n continue\n }\n this.#replayEarly(entry)\n }\n }\n\n // One queued event, replayed once per matching descriptor — Stimulus calls\n // each binding with the SAME event object, so runOps' duplicate-binding\n // guard behaves as it does live. Each descriptor is re-checked here, where\n // Stimulus would check it: it must still be on the element (a morph may\n // have removed it), its key filter must match under the app's own key\n // mappings (early.js knows only Stimulus's defaults), and a :once one runs a single time however often it\n // was queued (spendEarlyOnce).\n #replayEarly({ event, el, descs, win }) {\n const replay = earlyReplayEvent(event, el, win)\n const keyMappings = this.application?.schema?.keyMappings\n const tokens = (el.getAttribute(\"data-action\") ?? \"\").split(/\\s+/)\n // A window-bound entry belongs to this root, not to `el` (issue #303).\n const owner = win ? this.element : el\n for (const desc of descs) {\n if (!tokens.includes(desc.token)) continue\n if (desc.filter && !keyFilterMatches(desc.filter, event, keyMappings)) continue\n // (`:once` is read off the token: early.js keeps its records minimal.)\n if (/#\\w+.*:once\\b/.test(desc.token)) {\n if (!spendEarlyOnce(owner, desc, win && JSON.stringify(replay.params))) continue\n this.#earlySpentOn.add(owner)\n }\n // The original may still be propagating (issue #274): its live arrival at\n // `el` — or at the window, for a window-bound entry — must not run this\n // binding again (earlyOnceSwallows).\n markEarlyReplay(event, owner, win ? `@${desc.method}` : desc.method)\n if (desc.method === \"runOps\") this.runOps(replay)\n else this.dispatch(replay)\n }\n }\n\n // Tear down any pending debounce timers when the controller leaves the DOM\n // (Turbo morph/navigation removes the element). Otherwise a timer that hasn't\n // fired yet would later call #enqueue on a disconnected controller — a round\n // trip against a detached element / stale token (issue #17 follow-up).\n // Throttle suppression timers (issue #80) are torn down the same way — a\n // leading-edge timer holds no pending POST, but leaving it running would leak\n // it past the element's life.\n disconnect() {\n // Features FIRST (issue #275): the persist feature flushes a pending draft\n // write while the fields are still readable (Turbo disconnects before\n // leaving the page — a fast visit otherwise loses the last keystrokes).\n if (__SPLIT__) this.#disconnectFeatures()\n else this.#disconnectFeaturesNow()\n this.#clearAllDebounces()\n this.#clearAllThrottles()\n if (this.#boundRootMorph) {\n this.element.removeEventListener?.(\"turbo:morph-element\", this.#boundRootMorph)\n this.element.removeEventListener?.(\"focusin\", this.#boundRootMorph)\n this.element.removeEventListener?.(STREAMED, this.#boundRootMorph)\n }\n // Early triggers (issue #273): a disconnected root records again.\n earlyState().connected.delete(this.element)\n if (rootControllers.get(this.element) === this) rootControllers.delete(this.element)\n this.element.removeAttribute?.(\"data-reactive-connected\")\n // A spent :once replay is remembered per element so a morph REPLY (root\n // still connected) cannot re-arm it. A disconnect drops Stimulus's own\n // `once` listeners and the next connect binds fresh ones, so the memory\n // goes too (issue #274: a dormant morph-back disconnects a root in place —\n // its :once trigger must work again after the re-wake).\n for (const el of this.#earlySpentOn) spentEarlyOnce.delete(el)\n this.#earlySpentOn.clear()\n }\n\n // Serialize requests per component. Each round trip rewrites the signed\n // token in the DOM (state lives in the token, not the client). If events\n // fire faster than round trips complete, concurrent requests would all read\n // the SAME stale token and clobber each other (last-write-wins). Chaining on\n // a per-controller promise makes each dispatch wait for the previous one, so\n // it always uses the freshest token.\n dispatch(event) {\n // Issue #410: a client-only root has no action of its own — a server\n // trigger inside it belongs to the token-bearing root above. FIRST, so that\n // root runs every check below with its own element.\n if (event.params?.action && event.params.action !== LAZY_MATERIALIZE_ACTION && this.#clientOnly()) {\n return this.#forwardToServerRoot(event)\n }\n // A shortcut (issue #357) that is the other platform's twin, or pressed\n // in a field the user is typing in, is a complete no-op: the native\n // default (text undo) proceeds and nothing is announced. Checked FIRST: a\n // delete/backspace shortcut (issue #366) binds a bare window keydown, so\n // every key the user presses lands here.\n const shortcut = event.params?.shortcut\n if (shortcut != null && !shortcutApplies(shortcut, event, this.application?.schema?.keyMappings)) return\n // A :once trigger already replayed on connect (issue #273) is spent.\n if (earlyOnceSwallows(event, \"dispatch\", this.application?.schema?.keyMappings, this.element)) return\n // `window` (renamed: never shadow the global) and `outside` are the event-\n // modifier params (issue #80). The client decides preventDefault behavior\n // from event.params — set by the Ruby on() — never by sniffing the\n // Stimulus descriptor.\n const { action, params, debounce, throttle, confirm, confirmWhen, outside, window: windowBound, optimistic } =\n event.params\n if (!action) return\n\n // The pending-state hint (issue #181): data-reactive-busy-param. During a\n // deploy overlap a page rendered by the PREVIOUS gem still emits the old\n // data-reactive-loading-param — read it as a fallback so an in-flight page\n // keeps its pending affordance until the next full render (the hints\n // feature remaps its `class:` key).\n const busy = event.params.busy ?? event.params.loading\n\n // Outside guard FIRST (issue #80): an outside: trigger only fires for\n // events whose target is OUTSIDE this component's ROOT (containment against\n // this.element — .contains includes the root itself). An event inside the\n // root must be a COMPLETE no-op — before preventDefault (the page's native\n // click behavior is untouched) and before the reactive:before-dispatch\n // lifecycle event (nothing to announce, nothing to veto).\n if (outside && this.element.contains(event.target)) return\n\n // The trigger is event.currentTarget — the element on(...) was spread onto —\n // NOT event.target (issue #99). A `<button><span>Save</span></button>` click\n // has target === the span, which carries no params and is the wrong element\n // to disable / swap text on. currentTarget is the bound element; fall back to\n // target for a directly-invoked/synthetic event. Captured now because\n // #proceed runs in a later microtask (after the confirm resolver), by which\n // point currentTarget is reset to null.\n const target = event.currentTarget ?? event.target\n\n // Stop native behavior (button submit / FORM NAVIGATION) HERE, synchronously\n // within the event dispatch — BEFORE the (possibly async) confirm gate below.\n // preventDefault() only works while the event is still being handled; once we\n // await the confirm resolver it's too late, and a `submit` trigger would\n // natively POST the form and navigate before the reactive round trip runs\n // (issue #11). For a `click` trigger there's no default to miss. This holds\n // for debounced triggers too — the round trip is deferred, but the native\n // default must still be prevented now. (Moved ahead of the confirm branch in\n // issue #55: an async resolver means we can't preventDefault after awaiting.)\n //\n // ONLY for element-bound triggers: a window-bound trigger (window:/outside:,\n // issue #80) hears EVERY matching event on the page — preventDefault-ing\n // those would kill every link click while a dropdown is mounted. The page's\n // native behavior proceeds alongside the reactive round trip.\n //\n // The `checked: :keep` optimistic hint (issue #98) OPTS OUT: for a click-bound\n // checkbox/radio the unconditional preventDefault is exactly what stops the\n // native flip from happening before the morph — so a bare checkbox click\n // (which has no form-navigation default to lose) skips it and flips now, and\n // the failure revert snaps it back. A `change`-bound trigger is unaffected —\n // `change` isn't cancelable, so preventDefault was already a no-op there.\n //\n // A shortcut (issue #357) IS prevented: its key filter has already narrowed\n // the window binding to one chord the page claims (mod+s must not also open\n // the browser's save dialog).\n if ((shortcut != null || !windowBound) && !this.#keepsNativeToggle(optimistic, target)) event.preventDefault()\n\n // A reactive_lazy(on:) shell's trigger (issue #276) goes through the one\n // materialize entry point, which dedupes it against the observer, the\n // re-armed listener and a morph-back. That entry point lives in the defer\n // feature (issue #275): the synchronous part of this dispatch is done, and\n // the load itself waits for the feature while it is still on its way.\n if (action === LAZY_MATERIALIZE_ACTION) {\n // Without the feature (it failed to load) a shell can still load the\n // plain way: the signed __materialize POST. A `cache:` shell carries no\n // token (issue #306) and has no such way: the feature's reactive:error\n // already said so, and nothing is sent.\n const materialize = () => {\n // (A shell whose defer connect threw has nothing wired: the plain POST.)\n if (!__SPLIT__) {\n if (this.#featuresOn & DEFER) return deferFeature.materialize(this)\n } else {\n const defer = this.#features.get(\"defer\")\n if (defer) return defer.materialize(this)\n }\n if (this.tokenValue) return this.#proceed(target, action, \"{}\")\n }\n return this.#featuresSettling ? this.featuresReady.then(materialize) : materialize()\n }\n\n // Resolve the EFFECTIVE confirm message (issue #179): a plain string confirm:\n // is that string (static, #52); a Hash confirm: (confirmWhen) evaluates its\n // condition/predicate over the collected fields and returns the message ONLY\n // when it fires, else null → no dialog. No confirm at all → also null. The\n // conditional form is the bindings feature's: on the opt-in client its\n // answer may come a moment later (a promise), once the module is here.\n const message = this.#effectiveConfirmMessage(confirm, confirmWhen)\n const proceed = () => this.#proceed(target, action, params, debounce, throttle, optimistic, busy)\n\n // No message → proceed straight away (unchanged fast path).\n if (!message) return proceed()\n if (message instanceof Promise) {\n return message.then((resolved) => (resolved ? this.#confirmThen(resolved, target, proceed) : proceed()))\n }\n this.#confirmThen(message, target, proceed)\n }\n\n // The confirmation gate (issue #52, made overridable + async in #55). A\n // reactive trigger can't use Hotwire's data-turbo-confirm — this controller\n // preempts the event — so a `confirm:` message routes through\n // confirmResolver (default window.confirm; an app can override it to reuse\n // Turbo.config.forms.confirm). The resolver may be sync or async; call it\n // INSIDE the chain (via the leading .then) so even a SYNCHRONOUS override\n // throw rejects this promise instead of escaping dispatch — a throwing\n // dialog is treated as a cancel, like the user dismissing it. The .catch is\n // scoped to the resolver step (→ false = cancel), so a dismissed/erroring\n // dialog never surfaces as an unhandled rejection AND a genuine bug inside\n // #proceed is NOT silently swallowed. Enqueue ONLY on a truthy resolution —\n // nothing is enqueued, no timer scheduled, otherwise. The resolver's\n // optional 2nd arg (issue #222) carries the trigger element, so an override\n // has the same ctx shape here as on nestedRemove ({ el, … }).\n #confirmThen(message, target, proceed) {\n return Promise.resolve()\n .then(() => confirmResolver(message, { el: target }))\n .catch(() => false)\n .then((ok) => {\n if (ok) proceed()\n })\n }\n\n // The effective confirm message (issue #179): the static string; or, for a\n // conditional confirm, what the bindings feature says — now when the module\n // is here, else a promise of it (the opt-in client's import window); or\n // null when neither applies. A module that cannot load, or is slower than\n // the feature timeout, answers null: no dialog, the endpoint's\n // authorize/default-deny is the real gate.\n #effectiveConfirmMessage(confirm, confirmWhen) {\n if (confirm) return confirm\n if (!confirmWhen) return null\n if (!__SPLIT__) return bindingsFeature.confirmMessage(this, this.#featureCore(), confirmWhen)\n else {\n const loaded = featureModules.get(\"bindings\")\n if (loaded) return loaded.confirmMessage(this, this.#featureCore(), confirmWhen)\n return this.#awaitFeatureOrNull(\"bindings\").then(\n (bindings) => bindings && bindings.confirmMessage(this, this.#featureCore(), confirmWhen),\n )\n }\n }\n\n // A feature's module, or null once its import failed (logged) or outlasted\n // the feature timeout — for the two places a REQUEST waits on an import\n // without a root connection to wait with (a hint, a conditional confirm).\n #awaitFeatureOrNull(name) {\n if (__SPLIT__) {\n let timer\n return Promise.race([\n loadFeature(name).catch((error) => (logFeatureFailure(name, \"load\", error), null)),\n new Promise((resolve) => (timer = setTimeout(() => resolve(null), featureTimeoutMs()))),\n ]).finally(() => clearTimeout(timer))\n }\n }\n\n\n // A tokenless client-only root (issue #410). tokenValue is the Stimulus view\n // of data-reactive-token-value; the attribute check also rules out a shell.\n #clientOnly() {\n return !this.tokenValue && clientOnlyRoot(this.element)\n }\n\n // Hand a server trigger to the nearest token-bearing root above (issue #410).\n // A dormant one is woken (as early.js wakes a root on its own trigger) and\n // runs the dispatch when it connects. With no such root nothing is sent.\n #forwardToServerRoot(event) {\n const root = serverRoot(this.element.parentElement, ANY_ROOT)\n if (!root) {\n if (this.element.closest?.('[data-reactive-verbose=\"true\"], [data-reactive-debug=\"true\"]'))\n console.warn(`[phlex-reactive] \"${event.params.action}\": no token on this root or above, nothing sent`)\n return\n }\n const controller = rootControllers.get(root)\n if (controller) return controller.dispatch(event)\n // Held past this event's dispatch: cancel its native default NOW, by\n // dispatch()'s own rule, or a submit/link would act before the request.\n const { shortcut, window: windowBound, optimistic } = event.params\n const target = event.currentTarget ?? event.target\n if ((shortcut != null || !windowBound) && !this.#keepsNativeToggle(optimistic, target)) event.preventDefault()\n heldForwards.set(root, [...(heldForwards.get(root) ?? []), [event, target]])\n const data = root.dataset\n if (data.reactiveDormant) {\n data.controller = `${data.controller ?? \"\"} ${data.reactiveDormant}`.trim()\n delete data.reactiveDormant\n }\n }\n\n // CLIENT-ONLY trigger entry point (issue #95) — the zero-round-trip sibling\n // of dispatch(). Wired by on_client: applies the declared op chain\n // (data-reactive-ops-param, built by Phlex::Reactive::JS) locally. NO token,\n // NO params, NO fetch, ever. Ops are ephemeral UI: any server re-render of\n // the component resets whatever they toggled (by design — a signed action\n // owns state that must survive re-renders).\n runOps(event) {\n // A lone shortcut record (Stimulus typecasts it to an object) whose chord\n // did not fire bails before any bookkeeping: a delete/backspace shortcut\n // (issue #366) binds a bare window keydown that hears every key. Several\n // records are checked one by one below.\n const lone = event.params?.ops\n if (lone?.shortcut != null && !shortcutApplies(lone.shortcut, event, this.application?.schema?.keyMappings)) return\n if (earlyOnceSwallows(event, \"runOps\", this.application?.schema?.keyMappings, this.element)) return\n if (bindingsAlreadyRan(event, this)) return\n const params = event.params ?? {}\n // The trigger element on_client was spread onto (issue #222 ctx: { el }),\n // captured now — currentTarget resets before the confirm resolver's microtask.\n const trigger = event.currentTarget ?? event.target\n // Issue #271: run only the binding record(s) whose descriptor fired. A\n // legacy [[op, args]] attr reads its flags from the element-wide params.\n const records = parseBindingRecords(params.ops)\n // Stimulus already applied the firing descriptor's key filter (custom\n // schema keys included), so a lone candidate on type + window-boundness\n // runs without a second key check; several same-type candidates are told\n // apart by their key filters, read through the app's Stimulus schema.\n const candidates = records.filter((record) => bindingMatches(record, event, null))\n const matching =\n candidates.length <= 1\n ? candidates\n : candidates.filter((record) => bindingMatches(record, event, this.application?.schema?.keyMappings))\n if (matching.length === 0 && records.length > 0) {\n // Issue #271: a hand-edited attr or a descriptor the matcher doesn't\n // know. Verbose gate only (the devtools feature dedupes).\n if (this.#verboseEnabled()) {\n if (__SPLIT__) this.#devtools((devtools) => devtools.noBinding(this, event))\n else devtoolsFeature.noBinding(this, event)\n }\n return\n }\n for (const record of matching) {\n // An early replay never runs an outside: record (issue #303: an outside\n // click before connect had nothing to close).\n if (record.outside && event[EARLY_KEY]) continue\n // A detail miss (issue #346) is a complete no-op — checked before the\n // once record is spent, preventDefault and the confirm gate.\n if (!detailMatches(record, event)) continue\n // So is a shortcut that is not this platform's twin or fired in an\n // editable target (issue #357).\n if (record.shortcut != null && !shortcutApplies(record.shortcut, event, this.application?.schema?.keyMappings))\n continue\n if (onceBindingSpent(this, event, record)) continue\n // A legacy attr reads its flags element-wide — but never a mix-ed on()'s\n // debounce/throttle params (issue #393): those belong to the dispatch.\n this.#runBinding(\n record.legacy ? { ...params, ops: record.ops, debounce: undefined, throttle: undefined } : record,\n event,\n trigger,\n records.indexOf(record),\n )\n }\n }\n\n // One on_client binding (issue #95): its outside guard, preventDefault rule,\n // confirm gate, then its ops — every flag read from the binding itself.\n #runBinding(record, event, trigger, ordinal) {\n const { ops, confirm, confirmWhen, outside, window: windowBound, shortcut } = record\n // Outside guard FIRST — identical semantics to dispatch() (issue #80): an\n // outside: trigger is a COMPLETE no-op for events inside this root, before\n // preventDefault and before any op runs.\n if (outside && this.element.contains(event.target)) return\n\n // Element-bound triggers preventDefault (a bare button inside a <form>\n // must not submit it); window-bound triggers (window:/outside:) never do —\n // they hear every matching event on the page, and preventDefault-ing those\n // would kill native clicks site-wide (issue #80 rationale). Runs BEFORE the\n // (possibly async) confirm gate below — a native default can't wait for a\n // pending dialog (same ordering as dispatch()).\n // A shortcut (issue #357) is prevented: it is one chord the page claims.\n if (shortcut != null || !windowBound) event.preventDefault()\n\n // Resolve the effective confirm message — static string, or the conditional\n // Hash form (issue #179) evaluated over collected fields (a promise of it\n // on the opt-in client while the bindings module is on its way). Null →\n // no dialog. The gate is here (the user gesture), NOT in #applyOps: that\n // applier is shared with the server-pushed reactive:js stream action,\n // which must NEVER prompt. The same confirmResolver gate on(:action,\n // confirm:) uses (issues #52/#55/#178), so a themed dialog covers both.\n const message = this.#effectiveConfirmMessage(confirm, confirmWhen)\n const apply = this.#waitedOps(record, ordinal, trigger, () => this.#applyOps(this.#parseOps(ops)))\n if (!message) return apply()\n if (message instanceof Promise) {\n return message.then((resolved) => (resolved ? this.#confirmThen(resolved, trigger, apply) : apply()))\n }\n this.#confirmThen(message, trigger, apply)\n }\n\n // Issue #393: an on_client record's debounce:/throttle: — on()'s timers\n // (#debounce trailing, #throttle leading), keyed on the trigger + the\n // record's ordinal + JSON (two identical mix-ed records keep their own\n // timers), so disconnect clears them with the dispatch ones. The wait sits\n // after the outside guard, preventDefault and confirm, as on()'s does.\n #waitedOps(record, ordinal, trigger, run) {\n const debounce = Number(record.debounce) || 0\n const throttle = Number(record.throttle) || 0\n if (debounce <= 0 && throttle <= 0) return run\n const key = JSON.stringify([ordinal, record])\n if (debounce > 0) return () => this.#debounce(trigger, key, debounce, run)\n return () => this.#throttle(trigger, key, throttle, run)\n }\n\n // Client-side compute (issue #104) is the compute feature's (issue #275,\n // features/compute.js). This is the method its descriptor names\n // (input->reactive#recompute), so it stays. While the module is on its way\n // (the opt-in client) the entry records the edit, and the module runs one\n // recompute when it connects.\n recompute(event) {\n return __SPLIT__\n ? featureModules.get(\"compute\")?.recompute(this, this.#featureCore(), event)\n : computeFeature.recompute(this, this.#featureCore(), event)\n }\n\n // Tag-chip input (issue #203) and draft nested rows (issue #208) are the\n // bindings feature's (issue #275, features/bindings.js). These are the\n // methods their descriptors name, so they stay: each hands the event to the\n // module — now, or, with the opt-in client, once it has arrived. The event\n // is dead by then, so what the module reads of it (the trigger, the target)\n // is captured here; the native default is prevented now, as the module\n // would have. tagsAdd keeps its two composition guards (an Enter that\n // listnav already took, or is about to take) in the same tick.\n tagsAdd(event) {\n if (event?.defaultPrevented) return\n if (this.#listnavOptions(event).some((el) => el.hasAttribute?.(\"data-reactive-highlighted\"))) return\n return __SPLIT__\n ? this.#bindingsAction(\"tagsAdd\", event)\n : bindingsFeature.tagsAdd(this, this.#featureCore(), event)\n }\n\n tagsPick(event) {\n return __SPLIT__\n ? this.#bindingsAction(\"tagsPick\", event)\n : bindingsFeature.tagsPick(this, this.#featureCore(), event)\n }\n\n tagsRemove(event) {\n return __SPLIT__\n ? this.#bindingsAction(\"tagsRemove\", event)\n : bindingsFeature.tagsRemove(this, this.#featureCore(), event)\n }\n\n nestedAdd(event) {\n return __SPLIT__\n ? this.#bindingsAction(\"nestedAdd\", event)\n : bindingsFeature.nestedAdd(this, this.#featureCore(), event)\n }\n\n nestedRemove(event) {\n return __SPLIT__\n ? this.#bindingsAction(\"nestedRemove\", event)\n : bindingsFeature.nestedRemove(this, this.#featureCore(), event)\n }\n\n syncNestedJson(event) {\n return __SPLIT__\n ? featureModules.get(\"bindings\")?.syncNestedJson(this, this.#featureCore(), event)\n : bindingsFeature.syncNestedJson(this, this.#featureCore(), event)\n }\n\n #bindingsAction(name, event) {\n if (__SPLIT__) {\n const loaded = featureModules.get(\"bindings\")\n if (loaded) return loaded[name](this, this.#featureCore(), event)\n event?.preventDefault?.()\n const snapshot = { currentTarget: event?.currentTarget ?? null, target: event?.target ?? null, preventDefault() {} }\n return withFeature(\"bindings\", (bindings) => bindings[name](this, this.#featureCore(), snapshot))\n }\n }\n\n // Dirty tracking (issue #103) is the form feature's (issue #275,\n // features/form.js). This is the method its descriptor names\n // (input->reactive#trackDirty), so it stays: a full re-scan, by the module\n // when it is here. While it is still on its way nothing is lost — it scans\n // the whole root when it connects.\n trackDirty() {\n if (!__SPLIT__) formFeature.scan(this, this.#featureCore())\n else (this.#features.get(\"form\") ?? featureModules.get(\"form\"))?.scan(this, this.#featureCore())\n }\n\n // Client-side list navigation (combobox keyboard nav, issue #72). Wired by\n // on(:search, …, listnav: \"[role=option]\"), which appends keyboard filters to\n // the input's data-action (keydown.down/up/enter/esc->reactive#listnav*) and\n // sets data-reactive-listnav-option-param. Arrow keys move a highlight among\n // the options WITH NO ROUND TRIP; Enter picks the highlighted option by\n // CLICKING IT (so its own on(:select) reactive trigger fires — selection stays\n // a signed action); Escape clears. Ephemeral highlight state lives on the DOM\n // (data-reactive-highlighted), never shipped to the client as trusted state.\n //\n // Issue #271: a container spread with reactive_listnav(focus: true) carries\n // data-reactive-listnav-focus-param=\"true\" — ROVING-FOCUS mode for a\n // role=menu: the same moves shift real focus among the items instead of a\n // highlight, and Home/End (listnavFirst/listnavLast) jump to the edges.\n listnavNext(event) {\n this.#listnavMove(event, (current, length) => (current < 0 ? 0 : (current + 1) % length))\n }\n\n listnavPrev(event) {\n this.#listnavMove(event, (current, length) => (current < 0 ? length - 1 : (current - 1 + length) % length))\n }\n\n listnavFirst(event) {\n this.#listnavMove(event, () => 0)\n }\n\n listnavLast(event) {\n this.#listnavMove(event, (_current, length) => length - 1)\n }\n\n // Enter: activate the highlighted option (fires its reactive select). No-op if\n // nothing is highlighted, and in that case DON'T preventDefault — Enter falls\n // through (there's no selection to make).\n listnavPick(event) {\n const options = this.#listnavOptions(event)\n const current = options.findIndex((el) => el.hasAttribute(\"data-reactive-highlighted\"))\n if (current < 0) return\n event.preventDefault()\n options[current].click()\n }\n\n listnavClose(event) {\n for (const el of this.#listnavOptions(event)) el.removeAttribute(\"data-reactive-highlighted\")\n }\n\n // Move among THIS root's options: `pick(current, length)` returns the next\n // index (current is -1 when nothing is highlighted/focused). preventDefault\n // stops Arrow keys moving the caret in a search input, and the page scrolling\n // under a menu. Highlight mode writes data-reactive-highlighted; focus mode\n // (issue #271) focuses the chosen item, current being the item that is or\n // contains document.activeElement.\n #listnavMove(event, pick) {\n const options = this.#listnavOptions(event)\n if (!options.length) return\n event.preventDefault()\n\n const focusMode = this.#listnavFocusMode(event)\n const active = globalThis.document?.activeElement\n const current = focusMode\n ? options.findIndex((el) => el === active || (active != null && el.contains?.(active)))\n : options.findIndex((el) => el.hasAttribute(\"data-reactive-highlighted\"))\n const chosen = options[pick(current, options.length)]\n\n if (focusMode) {\n chosen.focus?.()\n } else {\n for (const el of options) el.removeAttribute(\"data-reactive-highlighted\")\n chosen.setAttribute(\"data-reactive-highlighted\", \"true\")\n }\n chosen.scrollIntoView?.({ block: \"nearest\" })\n }\n\n // Focus mode is read off the TRIGGER like the option selector (issue #271).\n #listnavFocusMode(event) {\n const trigger = event?.currentTarget ?? event?.target ?? this.element\n return trigger.getAttribute?.(\"data-reactive-listnav-focus-param\") === \"true\"\n }\n\n // The option elements this root owns (skips nested reactive roots, issue #15),\n // per the selector on data-reactive-listnav-option-param. The attr rides on the\n // TRIGGER element (the search input on(...) is spread onto), read from the\n // event; the options are still scoped to this controller's root. Empty when\n // unset. Falls back to the root for a directly-invoked call (unit tests). The\n // ownership predicate is hoisted ONCE per keypress (issue #117) — in the common\n // no-nested-root case it is a constant true, skipping a closest() walk per\n // option. Hidden options are excluded (issue #163): a reactive_filter (or any\n // `hidden` toggle) removes a row from the keyboard path too, so an Arrow can't\n // highlight — and Enter can't pick — an invisible option.\n #listnavOptions(event) {\n const trigger = event?.currentTarget ?? event?.target ?? this.element\n const selector =\n trigger.getAttribute?.(\"data-reactive-listnav-option-param\") ??\n this.element.getAttribute(\"data-reactive-listnav-option-param\")\n if (!selector) return []\n const owns = this.#ownershipFilter()\n return Array.from(this.element.querySelectorAll(selector)).filter((el) => !el.hidden && owns(el))\n }\n\n // Enqueue the action — debounced if a debounce window is set, else immediately.\n // Split out of dispatch so both the no-confirm fast path and the post-confirm\n // microtask share one place (issue #55). `target` is captured up front because\n // this can run in a later microtask, after event.target has been reset.\n #proceed(target, action, params, debounce, throttle, optimistic, busy) {\n // Lifecycle veto point (issue #79): one cancelable reactive:before-dispatch\n // per user gesture — post-preventDefault, post-confirm, PRE-debounce (and\n // PRE-throttle, the same timing). event.preventDefault() skips the\n // debounce/throttle AND the enqueue entirely (nothing is scheduled).\n // retry() re-enters the queue directly, so this does NOT refire on a retry.\n // detail.params are the trigger's explicit params; sibling fields are\n // collected later, at send time.\n const before = this.#emit(\"reactive:before-dispatch\", {\n action,\n params: this.#parseParams(params),\n element: this.element,\n }, { cancelable: true })\n if (before.defaultPrevented) return\n\n // Debounced trigger (e.g. on(:update, event: \"input\", debounce: 300)):\n // coalesce rapid events into ONE round trip after a quiet period, instead of\n // one POST per keystroke (issue #17). A blur flushes a pending dispatch.\n // The optimistic hint (issue #98) and the busy state (issue #181) ride to\n // the flush too, so they apply ONCE per enqueue — a debounced input must not\n // flap toggle_class per keystroke, and its element must NOT be disabled\n // during the quiet period (that would break typing). Both apply at ENQUEUE.\n const ms = Number(debounce) || 0\n if (ms > 0) return this.#debounceDispatch(target, ms, action, params, optimistic, busy)\n\n // Throttled trigger (e.g. on(:track, event: \"scroll\", window: true,\n // throttle: 250), issue #80): LEADING-EDGE rate limit — fire the first\n // event immediately, drop the rest until the window elapses. debounce and\n // throttle are mutually exclusive (the Ruby on() raises on both).\n const throttleMs = Number(throttle) || 0\n if (throttleMs > 0) return this.#throttleDispatch(target, throttleMs, action, params, optimistic, busy)\n\n return this.#enqueue(action, params, optimistic, target, busy)\n }\n\n // Apply the optimistic hint ONCE (recording its inverse) and chain the round\n // trip, threading that inverse onto THIS queued request so the serialized\n // per-controller queue reverts the RIGHT request's hint on failure (issue\n // #98). Applying here — the single flush/enqueue point every path funnels\n // through — is what makes a hint apply once per enqueue, not per raw dispatch.\n //\n // The busy state (issue #181) applies here too, for the same reason: enqueue\n // is the moment the request is committed to the queue, so the always-on busy\n // vocabulary (data-reactive-busy on the trigger + root, aria-busy via a pending\n // counter, busy_on scoping) and the busy hint (disable + class + text swap)\n // cover the WHOLE pending window — queue wait included — not just the fetch. It\n // returns a `settle` closure that #perform runs in its finally (success OR\n // failure), guarded so a morph-replaced trigger is never clobbered.\n #enqueue(action, params, optimistic, target, busy) {\n // The always-on busy state and the activity signal, now: they cover the\n // whole pending window, a wait for the hints module included. `pending`\n // is what this request's hint engine recorded — filled in by the apply\n // below, which may run a moment later than this call.\n const pending = { inverse: null, undo: [], resurrect: null }\n const settle = this.#applyBusy(action, target, pending)\n const wanted = Boolean(optimistic || busy)\n const apply = (hints) => {\n if (!wanted || !hints) return\n const core = this.#featureCore()\n pending.inverse = __SPLIT__\n ? hints.optimistic(this, core, optimistic, target)\n : hintsFeature.optimistic(this, core, optimistic, target)\n pending.undo.push(...(__SPLIT__ ? hints.busy(this, core, busy, target) : hintsFeature.busy(this, core, busy, target)))\n // Debug-only teaching aid (issue #181): if optimistic: { hide: true } is\n // used for instant-delete but the reply RE-RENDERS the element (bringing\n // it back), that hint was pointless — the developer likely wanted\n // reply.remove. Capture the hidden nodes now; the success path re-checks\n // the OBSERVED DOM after the morph (never inferred from the verb).\n if (this.#debugEnabled()) {\n pending.resurrect = __SPLIT__\n ? hints.resurrection(this, core, optimistic, target)\n : hintsFeature.resurrection(this, core, optimistic, target)\n }\n }\n // A hint applies ONCE per enqueue — the single flush point every path\n // funnels through — never per raw dispatch. With the module here (the\n // default client, always) that is now, synchronously.\n const loaded = __SPLIT__ ? featureModules.get(\"hints\") : true\n if (!wanted || loaded) apply(loaded)\n // A feature still loading may be about to change what this request reads\n // (issue #275: the draft restore writes the fields #perform collects). Only\n // such a feature — one that `gates` — holds a request back, until it has\n // connected: at most the feature timeout, and on a root that never waited\n // for one, not at all. A feature that does not gate (the defer module)\n // never delays a request, however slow its import. A request WITH a hint\n // on a page that has not loaded the hints module waits for that import\n // the same way (at most the feature timeout), then applies the hint and\n // goes out — once.\n const perform = () => this.#perform(action, params, pending, settle)\n const ready = () => (this.#featureGate ? this.#featureGate.then(perform) : perform())\n this.queue = (this.queue ?? Promise.resolve()).then(() =>\n wanted && !loaded ? this.#awaitHints().then(apply).then(ready) : ready(),\n )\n return this.queue\n }\n\n // The hints module, or null once its import failed or outlasted the feature\n // timeout (the request then goes out without its hint).\n #awaitHints() {\n return this.#awaitFeatureOrNull(\"hints\")\n }\n\n // Reset a per-element timer; only enqueue the round trip after `ms` of quiet.\n // Also flush immediately on blur so leaving the field never drops the last\n // edit (a long debounce shouldn't swallow a value the user tabbed away from).\n #debounceDispatch(target, ms, action, params, optimistic, busy) {\n this.#debounce(target, DISPATCH_WAIT_KEY, ms, () => this.#enqueue(action, params, optimistic, target, busy))\n }\n\n // The one trailing-edge timer on() and on_client (issue #393) share. Timers\n // are keyed on target + key: on()'s dispatch uses DISPATCH_WAIT_KEY, an\n // on_client record its ordinal + JSON (it starts with \"[\"), so an on() debounce and an\n // on_client debounce on one input never cancel each other. A window-bound\n // trigger gets no blur flush: a window blur is a tab switch, not an edit.\n #debounce(target, key, ms, run) {\n this.#clearDebounce(target, key)\n\n const flush = () => {\n this.#clearDebounce(target, key)\n run()\n }\n const timer = setTimeout(flush, ms)\n if (!isWindow(target)) target?.addEventListener?.(\"blur\", flush, { once: true })\n let pending = this.#debounceTimers.get(target)\n if (!pending) this.#debounceTimers.set(target, (pending = new Map()))\n pending.set(key, { timer, flush })\n }\n\n #clearDebounce(target, key) {\n const byKey = this.#debounceTimers.get(target)\n const pending = byKey?.get(key)\n if (!pending) return\n clearTimeout(pending.timer)\n target?.removeEventListener?.(\"blur\", pending.flush)\n byKey.delete(key)\n if (byKey.size === 0) this.#debounceTimers.delete(target)\n }\n\n // Clear every pending debounce timer (used on disconnect). Reuses\n // #clearDebounce so all timer/listener teardown stays in one place. Snapshot\n // the entries first — #clearDebounce mutates the maps as it goes.\n #clearAllDebounces() {\n for (const [target, byKey] of [...this.#debounceTimers]) {\n for (const key of [...byKey.keys()]) this.#clearDebounce(target, key)\n }\n }\n\n // Leading-edge throttle (issue #80), mirroring #debounceDispatch: the FIRST\n // event fires immediately; a suppression timer then drops further events\n // until the window elapses (no trailing fire — dropped, not queued). Timers\n // are keyed on action + target, NOT target alone: window-bound scroll/resize\n // events all share event.target === document, so two window-bound triggers\n // on one component would otherwise collide on one timer.\n #throttleDispatch(target, ms, action, params, optimistic, busy) {\n return this.#throttle(target, action, ms, () => this.#enqueue(action, params, optimistic, target, busy))\n }\n\n // The leading-edge window on() and on_client (issue #393) share; an\n // on_client record keys on its ordinal + JSON, which no action name can equal.\n #throttle(target, key, ms, run) {\n const timers = this.#throttleTimers.get(target) ?? new Map()\n if (timers.has(key)) return // inside the window — suppress\n\n const timer = setTimeout(() => {\n timers.delete(key)\n if (timers.size === 0) this.#throttleTimers.delete(target)\n }, ms)\n timers.set(key, timer)\n this.#throttleTimers.set(target, timers)\n return run() // leading edge: fire NOW\n }\n\n // Clear every throttle suppression timer (used on disconnect, alongside\n // #clearAllDebounces) so nothing outlives the element.\n #clearAllThrottles() {\n for (const timers of this.#throttleTimers.values()) {\n for (const timer of timers.values()) clearTimeout(timer)\n }\n this.#throttleTimers.clear()\n }\n\n // Raw-dispatch a lifecycle CustomEvent (issue #79). Deliberately NOT\n // Stimulus's this.dispatch() helper — that name is SHADOWED by this\n // controller's own dispatch(event) action method. Bubbling + composed so a\n // page-level listener (or `data-action=\"reactive:error->toast#show\"` on an\n // ancestor) hears it. After a plain (non-morph) replace this.element is a\n // DETACHED node — a bubbling event on it never reaches document listeners —\n // so fall back to dispatching on document itself.\n #emit(name, detail, { cancelable = false } = {}) {\n const event = new CustomEvent(name, { bubbles: true, composed: true, cancelable, detail })\n const root = this.element.isConnected ? this.element : document\n root.dispatchEvent(event)\n return event\n }\n\n // reactive:error detail: { action, params, kind, status?, body?, retry }.\n // `params` are the FULL params that were sent (collected fields + explicit\n // trigger params). retry() re-enters the request queue with the ORIGINAL raw\n // trigger params, so #perform re-reads the freshest token and RE-COLLECTS the\n // sibling fields — nothing stale is replayed. It does not refire\n // reactive:before-dispatch (one veto per user gesture), and it no-ops with a\n // warning once the root has left the DOM (retrying against a detached\n // element would post a stale token into nowhere).\n #emitError(action, rawParams, sentParams, extra) {\n const retry = () => {\n if (!this.element.isConnected) {\n console.warn(\"[phlex-reactive] retry() ignored — the reactive root left the DOM\")\n return\n }\n return this.#enqueue(action, rawParams)\n }\n this.#emit(\"reactive:error\", { action, params: sentParams, ...extra, retry })\n }\n\n // Mark the reactive root as errored (issue #100) with the failure kind, so an\n // app can style it purely in CSS ([data-reactive-error] { … }) with zero JS.\n // Guarded — a plain replace may have detached the root before the failure lands.\n #markError(kind) {\n if (this.element?.isConnected === false) return\n this.element?.setAttribute?.(\"data-reactive-error\", kind)\n }\n\n // Clear the failure marker on the next successful apply (issue #100).\n #clearError() {\n this.element?.removeAttribute?.(\"data-reactive-error\")\n }\n\n // Offline fallback (issue #100): a network failure reached no server, so there\n // is no body to render. Clone the content of a server-rendered\n // <template data-reactive-error-flash> into the flash region so the user still\n // sees SOMETHING. The template is app-authored (trusted) — this is a pure\n // deep clone, never client templating of untrusted data. No template, or no\n // flash container, is a silent no-op (a page without the opt-in is unchanged).\n #renderNetworkFallback() {\n const template = document.querySelector(\"[data-reactive-error-flash]\")\n if (!template?.content) return\n // The flash region is the host-app container Response#flash targets. Its id\n // defaults to \"flash\" (Phlex::Reactive.flash_target); an app that customized\n // it can point the fallback at the same node by putting the template's\n // data-reactive-error-flash value there — but the common case is #flash.\n const targetId = template.getAttribute(\"data-reactive-error-flash\") || \"flash\"\n const region = document.getElementById(targetId)\n if (!region) return\n region.appendChild(template.content.cloneNode(true))\n }\n\n // Latency simulator (issue #102): the delay itself is the dev feature's.\n // A request waits for the module only while a delay is stored (so the very\n // first delayed request is delayed too); with none stored this is two reads.\n #maybeSimulateLatency() {\n if (!__SPLIT__) return devtoolsFeature.delay()\n else {\n const devtools = featureModules.get(\"devtools\")\n if (devtools) return devtools.delay()\n if (latencyStored()) return loadFeature(\"devtools\").then((loaded) => loaded.delay(), () => {})\n }\n }\n\n // Client debug mode (issue #108) — the \"devtools-lite\" lens. On when the Ruby\n // reactive_attrs stamped data-reactive-debug=\"true\" (Phlex::Reactive.debug).\n // Read live (a single getAttribute) so the whole feature is inert for any app\n // that never opts in: OFF → this returns false and #perform builds no debug\n // object, parses no response, and logs nothing (the \"zero cost when off\"\n // invariant — one nil-check per dispatch). Guarded for a stub root with no\n // getAttribute (unit harnesses) so it degrades to off, never throwing.\n #debugEnabled() {\n return this.element?.getAttribute?.(\"data-reactive-debug\") === \"true\"\n }\n\n // A monotonic timestamp for the round-trip duration (ms). performance.now is\n // monotonic (immune to a wall-clock adjustment mid-request); Date.now is the\n // fallback for an exotic environment without it. ONLY called on the debug path,\n // so it costs nothing when debug is off.\n #debugNow() {\n return typeof performance !== \"undefined\" && typeof performance.now === \"function\"\n ? performance.now()\n : Date.now()\n }\n\n async #perform(action, params, pending, settle) {\n // Auto-collect named field values inside this component so a button-\n // triggered action still receives sibling inputs (Livewire-style), plus any\n // chosen file inputs in the SAME walk. Explicit params\n // (data-reactive-params-param) win over collected fields.\n const { fields, files } = this.#collectFields()\n // Parse the explicit trigger params ONCE — reused below for allParams and, on\n // the debug path, for the params-only name list (so the trace can show\n // `params: [...]` distinct from the collected `+ collected: [...]`). #parseParams\n // is pure (a fresh object from a JSON string, or the same object by reference\n // when already parsed); allParams spreads a COPY and nothing mutates parsedParams\n // downstream (JSON.stringify / #buildFormData read allParams, a new object).\n const parsedParams = this.#parseParams(params)\n const allParams = { ...fields, ...parsedParams }\n const token = this.#currentToken\n\n // File/multipart path (issue #34): if THIS root has a populated\n // <input type=\"file\">, the action can't be JSON (JSON.stringify drops the\n // File). Send FormData instead — token + act + scalar params as fields, each\n // chosen file appended. The morph/token machinery downstream is identical;\n // only the request ENCODING differs when files are present.\n const multipart = files.length > 0\n const body = multipart\n ? this.#buildFormData(token, action, allParams, files)\n : JSON.stringify({ token, act: action, params: allParams })\n\n // Client debug mode (issue #108): build the trace object ONLY when debug is on\n // (one nil-check otherwise — zero cost off). It carries NAMES only: the\n // explicit trigger param names and the collected sibling field names, split so\n // the group shows `params: [...] + collected: [...]`. status/streams/\n // tokenRefreshed are filled in at the branch that knows them; the group is\n // emitted once in the finally so EVERY exit (success or any failure) logs.\n const debug = this.#debugEnabled()\n ? {\n action,\n paramNames: Object.keys(parsedParams),\n fieldNames: Object.keys(fields),\n encoding: multipart ? \"multipart\" : \"json\",\n status: null,\n streams: [],\n tokenRefreshed: false,\n started: this.#debugNow(),\n }\n : null\n\n // aria-busy on the root is now driven by the loading pending counter\n // (#applyLoading, applied at ENQUEUE so it covers the queue wait too), not\n // set here. #settleLoading in the finally clears it — see #enqueue.\n\n // Latency simulator (issue #102): the busy window (aria-busy + loading state)\n // is already applied at enqueue, so awaiting the configured delay HERE — after\n // that window opened and before the fetch — is what makes it visible on\n // localhost. A null/absent/non-positive sessionStorage key is a no-op (zero\n // production surface), so this line vanishes for any app that never opts in.\n await this.#maybeSimulateLatency()\n\n try {\n // Offline gate (issue #101), authoritative at the NETWORK BOUNDARY (send\n // time), not at enqueue. A click can enqueue while online and reach here\n // after going offline (a debounced/queued request, a rapid transition) —\n // gating in #perform makes the kind consistently \"offline\" for that whole\n // condition instead of leaking through as a \"network\" fetch throw. The\n // fetch never fires, so the edit is not half-sent; the finally still runs\n // (settle clears loading), the optimistic hint reverts, and retry() (which\n // re-enters #perform) re-checks and sends once back online.\n if (navigator.onLine === false) {\n this.#revertOptimistic(pending.inverse)\n this.#markError(\"offline\")\n this.#emitError(action, params, allParams, { kind: \"offline\" })\n return\n }\n\n let response\n try {\n const headers = {\n Accept: \"text/vnd.turbo-stream.html\",\n \"X-CSRF-Token\": this.#csrfToken(),\n }\n // For JSON we declare the content type; for multipart we must NOT — the\n // browser sets `multipart/form-data; boundary=…` itself, and overriding it\n // would strip the boundary and corrupt the body server-side.\n if (!multipart) headers[\"Content-Type\"] = \"application/json\"\n // Send the pgbus SSE connection id (if subscribed) so the server can\n // exclude this connection from its own broadcast echo — the actor\n // already gets the action's HTTP response. Harmless without pgbus.\n const connectionId = this.#connectionId()\n if (connectionId) headers[\"X-Pgbus-Connection\"] = connectionId\n // A presence root's own connection (issue #358): the row its presence\n // writes land on, whatever source comes first on the page.\n const presence =\n presenceNeeded(this.element) &&\n document.getElementById(`reactive-presence-src-${this.element.id}`)?.getAttribute(\"connection-id\")\n if (presence) headers[\"X-Reactive-Presence\"] = presence\n // The fields a morph held over a changed server value (issue #370).\n const held = heldHeader(this.element)\n if (held) headers[\"X-Reactive-Held\"] = held\n\n // ONLY `fetch` itself is the network boundary — offline, DNS, a reset\n // connection. Everything below this inner try (reading the body,\n // extracting the token, handing streams to Turbo) runs AFTER the\n // server already processed the mutation, so none of it belongs in the\n // `kind: \"network\"` / retriable bucket (CodeRabbit review on #89): if\n // renderStreamMessage throws, retry()ing would re-POST an action the\n // server already completed — see the outer catch below. (NOT a\n // reactive:applied LISTENER throwing — per the DOM spec, dispatchEvent\n // never propagates a listener's exception back to its caller, so that\n // case can't reach this catch at all; verified in the JS test suite.)\n countReactiveRequest(\"action\")\n response = await fetch(this.#actionPath(), {\n method: \"POST\",\n headers,\n body,\n credentials: \"same-origin\",\n // Bound the request (issue #101): a server that never answers used to\n // wedge this.queue forever (the finally that clears aria-busy/loading\n // never ran). AbortSignal.timeout(ms) aborts the fetch after the\n // configured window; the abort surfaces in this catch as a\n // DOMException named \"TimeoutError\" (see the branch below).\n signal: AbortSignal.timeout(this.#timeoutMs()),\n })\n } catch (error) {\n console.error(\"[phlex-reactive] action error\", error)\n this.#revertOptimistic(pending.inverse)\n // AbortSignal.timeout() rejects with a DOMException named \"TimeoutError\"\n // (a manual AbortController.abort() would be \"AbortError\" — we don't use\n // one, but accept it too for robustness). A timeout is NOT \"offline\":\n // the request left and the server didn't answer in time; connectivity is\n // unknown. So do NOT clone the offline-fallback template or mark network\n // — just fire kind:\"timeout\" (retriable). The queue still advances\n // (#perform returns, never rejects), so the hung request un-wedges it.\n if (error?.name === \"TimeoutError\" || error?.name === \"AbortError\") {\n this.#markError(\"timeout\")\n this.#emitError(action, params, allParams, { kind: \"timeout\" })\n return\n }\n // No server reached — nothing to render (issue #100). Clone a\n // server-rendered <template data-reactive-error-flash> into the flash\n // region as an offline fallback. The template is rendered by the app\n // (trusted), so this is a pure clone — no client templating of data.\n this.#renderNetworkFallback()\n this.#markError(\"network\")\n this.#emitError(action, params, allParams, { kind: \"network\" })\n return\n }\n\n // Debug (issue #108): the server answered — record the status for the trace\n // now, so every response branch below (redirected/http/content-type/ok) logs\n // it. The transport-failure branches above return before here (they have no\n // status); their group still fires from the finally with status null → \"—\".\n if (debug) debug.status = response.status\n\n if (response.redirected) {\n console.error(\"[phlex-reactive] action was redirected (auth/CSRF?) — no update applied\")\n this.#revertOptimistic(pending.inverse)\n this.#markError(\"redirected\")\n this.#emitError(action, params, allParams, { kind: \"redirected\", status: response.status })\n return\n }\n if (!response.ok) {\n const errorBody = await response.text()\n console.error(`[phlex-reactive] action failed: HTTP ${response.status}`, errorBody)\n this.#revertOptimistic(pending.inverse)\n // Render a non-OK turbo-stream body so a server-rendered error flash\n // (an error_flash rescue, or a status: :unprocessable_entity validation\n // reply from a plain controller) is actually SHOWN — instead of being\n // read only for the console (issue #100). #extractToken is run as usual;\n // it NO-OPS when no stream re-renders our id, so a 400 InvalidToken body\n // never refreshes the held identity token (which is not a nonce and stays\n // retry-valid — do not \"fix\" that). A non-turbo-stream body is left to the\n // console.error above (an HTML error page must not be handed to Turbo).\n if ((response.headers.get(\"Content-Type\") || \"\").includes(\"turbo-stream\")) {\n const fresh = this.#extractToken(errorBody)\n this.#currentToken = fresh ?? this.#currentToken\n if (debug) {\n if (__SPLIT__) this.#devtools((devtools) => devtools.recordBody(debug, errorBody, fresh))\n else devtoolsFeature.recordBody(debug, errorBody, fresh)\n }\n renderOwnReply(this.element, errorBody, false)\n }\n this.#markError(\"http\")\n this.#emitError(action, params, allParams, { kind: \"http\", status: response.status, body: errorBody })\n return\n }\n\n const contentType = response.headers.get(\"Content-Type\") || \"\"\n if (!contentType.includes(\"turbo-stream\")) {\n console.error(`[phlex-reactive] expected a turbo-stream, got \"${contentType}\" — no update applied`)\n this.#revertOptimistic(pending.inverse)\n this.#markError(\"content-type\")\n this.#emitError(action, params, allParams, { kind: \"content-type\", status: response.status })\n return\n }\n\n const html = await response.text()\n // Capture the new token from the response synchronously, so the next\n // queued request uses it without waiting for the async DOM morph.\n const fresh = this.#extractToken(html)\n this.#currentToken = fresh ?? this.#currentToken\n // Debug (issue #108): record the stream actions/targets + whether a refresh\n // arrived, from the body we JUST read (reuse — no second text() read). Never\n // the token or template contents.\n if (debug) {\n if (__SPLIT__) this.#devtools((devtools) => devtools.recordBody(debug, html, fresh))\n else devtoolsFeature.recordBody(debug, html, fresh)\n }\n // Turbo applies the <turbo-stream> ops by id. A plain replace is an\n // outerHTML swap (focus on the replaced subtree is lost); a method=\"morph\"\n // replace (Response.morph) or an update morphs in place, preserving the\n // focused input + caret on unchanged nodes — see issue #28. As the root's\n // own reply, it clears what the root held (issue #370).\n renderOwnReply(this.element, html)\n // Debug-only (issue #181): the morph may apply a microtask later, so check\n // the resurrected-hide case AFTER it lands. Off the debug path, resurrect is\n // null — zero cost.\n if (pending.resurrect) queueMicrotask(pending.resurrect)\n // A successful apply CLEARS any prior failure marker (issue #100), so\n // error-driven CSS on the root (a red border, a shake) resets on recovery.\n this.#clearError()\n // Lifecycle hook (issue #79): the streams were HANDED TO Turbo — a\n // renderStreamMessage applies asynchronously, so the DOM mutation may\n // complete a tick later. Apps needing post-morph timing listen to Turbo's\n // own events; this one is for \"the action round trip succeeded\".\n this.#emit(\"reactive:applied\", { action, params: allParams, html })\n // What core.proceed resolves with: a feature can tell a reply that\n // applied from a failure (every other exit returns nothing).\n return html\n } catch (error) {\n // The server already processed this action successfully (we're past the\n // fetch) — a throw here is a CLIENT-side apply failure (a malformed\n // response, a broken Turbo render — NOT a reactive:applied listener\n // throw, which dispatchEvent never propagates here), not a transport\n // failure. kind: \"apply\" carries NO retry() — retrying would re-POST an\n // action the server already completed.\n console.error(\"[phlex-reactive] action error\", error)\n this.#revertOptimistic(pending.inverse)\n this.#emit(\"reactive:error\", { action, params: allParams, kind: \"apply\" })\n } finally {\n // Settle the loading state (issue #99): decrement the pending counter,\n // drop the trigger's/root's busy tokens, restore disabled/text/class —\n // guarded so a morph-replaced trigger is never clobbered. Runs on EVERY\n // exit (success, every failure branch, or an apply throw).\n settle?.()\n // Debug (issue #108): emit the group here so EVERY exit path logs exactly\n // once — success, any transport/response failure, or an apply throw. Null\n // when debug is off (zero cost). The round-trip ms is measured now, at the\n // finally, so it spans the whole #perform (fetch + apply) regardless of exit.\n if (debug) {\n if (__SPLIT__) this.#devtools((devtools) => devtools.trace(this, { ...debug, ms: this.#debugNow() - debug.started }))\n else devtoolsFeature.trace(this, { ...debug, ms: this.#debugNow() - debug.started })\n }\n }\n }\n\n get #currentToken() {\n return this.#tokenCache ?? this.tokenValue\n }\n\n set #currentToken(value) {\n this.#tokenCache = value\n }\n\n // A token written into the DOM wins over the cached one (issue #301): an\n // outside morph (a broadcast, a page refresh, a dormant morph-back) rewrites\n // the attribute while this controller survives, and Stimulus calls this on\n // that change and on a reconnect after it. Our own reply's morph writes the\n // token #extractToken already cached, so dropping the cache changes nothing —\n // unless a later reply was already extracted: then the attribute holds the\n // older token until that reply's morph lands, the same frame (Turbo renders\n // streams after nextRepaint), before any queued dispatch reads it.\n tokenValueChanged() {\n this.#tokenCache = undefined\n }\n\n // Read the next token for THIS controller — the one that re-renders THIS\n // element's id, never just the first token in the body (issue #46). On a\n // collection of REACTIVE rows the prepended/appended ROW carries its OWN\n // data-reactive-token-value and it sorts FIRST in the response; the list's own\n // fresh token rides a trailing `reactive:token` stream targeting the container.\n // Grabbing the first match stored the ROW's token, so the list's SECOND\n // dispatch sent a row token → failed verification → add-once-only. This mirrors\n // the server's carries_token_for? (#44): a stream carries OUR token only when it\n // RE-RENDERS our id (reactive:token / replace / update of `this.element.id`) —\n // append/prepend insert children and never count. Returns undefined when no\n // stream re-renders our id, so #currentToken keeps its existing value.\n #extractToken(html) {\n const id = this.element.id\n if (!id) {\n // No id to self-match (shouldn't happen for a reactive root). Fall back to\n // the legacy first-token behavior so a single-component response still works.\n return html.match(/data-reactive-token-value=\"([^\"]+)\"/)?.[1]\n }\n\n const { token, self } = this.#tokenRegexes(id)\n\n // The dedicated token-only refresh for THIS element (partial updates / the\n // collection container) — an attribute on the <turbo-stream> itself.\n const tokenStream = html.match(token)\n if (tokenStream) return tokenStream[1]\n\n // A full self re-render: a replace/update of THIS element whose template root\n // carries the fresh token. Scope the token search to that one stream so a\n // sibling/child token elsewhere in the body can't leak in.\n const selfStream = html.match(self)\n if (selfStream) return selfStream[1].match(/data-reactive-token-value=\"([^\"]+)\"/)?.[1]\n\n // Nothing re-rendered our id — keep the current token.\n return undefined\n }\n\n // The two per-id RegExps #extractToken uses to self-match this element's next\n // token (issue #118). `this.element.id` is page-stable, so these are compiled\n // ONCE and reused across every response — instead of allocating two fresh\n // RegExps per round trip. The memo is KEYED ON THE ID and rebuilt when it\n // changes: a re-render that re-identifies the root must scan for the NEW target,\n // never the stale one (or the token would freeze — see the id-change bun test).\n // The PATTERNS are byte-identical to the pre-memo inline literals; only their\n // allocation moved.\n #tokenRegexes(id) {\n const cache = this.#tokenRegexCache\n if (cache && cache.id === id) return cache\n const escaped = escapeRegExp(id)\n return (this.#tokenRegexCache = {\n id,\n token: new RegExp(\n `<turbo-stream\\\\b[^>]*\\\\baction=\"reactive:token\"[^>]*\\\\btarget=\"${escaped}\"[^>]*\\\\bdata-reactive-token-value=\"([^\"]+)\"`,\n ),\n self: new RegExp(\n `<turbo-stream\\\\b[^>]*\\\\baction=\"(?:replace|update)\"[^>]*\\\\btarget=\"${escaped}\"[^>]*>([\\\\s\\\\S]*?)</turbo-stream>`,\n ),\n })\n }\n\n // True when `el` is collected by THIS reactive root and not by a nested one.\n // A reactive component can be rendered inside another (both are\n // data-controller=\"reactive\" roots). querySelectorAll() descends into nested\n // roots, so without this guard an outer action would sweep the inner roots'\n // inputs into its own params (issue #15). An element belongs to this root iff\n // its nearest [data-controller~=\"reactive\"] ancestor is this.element.\n #ownsField(el) {\n return el.closest('[data-controller~=\"reactive\"]') === this.element\n }\n\n // A per-op ownership PREDICATE — the issue #117 fast path over issue #15\n // scoping. #ownsField answers \"is this element mine, not a nested reactive\n // root's\" with a per-element closest() walk; on a wide form or every keystroke\n // that walk runs per matched field. This hoists the DECISION to once per op.\n //\n // HYBRID GATE — closest() stays the source of truth; the nested-root query only\n // decides whether the fast path is SAFE:\n // * Fast path (overwhelmingly common): this root contains NO nested reactive\n // roots, so every element the caller's querySelectorAll returned is already\n // a direct descendant of this.element with no intervening reactive root —\n // it is ours. Return a constant-true predicate and skip the per-field\n // closest() walk entirely. That is the whole win.\n // * Nested case: fall back to the UNCHANGED #ownsField closest() check, so\n // scoping is byte-identical to before. We deliberately do NOT use\n // contains() here — the closest() form needs no node to implement\n // contains(), and on a real DOM the two agree for a descendant of\n // this.element (el.closest('[data-controller~=\"reactive\"]') === this.element\n // iff no nested reactive-root descendant contains el).\n //\n // Computed ONCE per dispatch-scoped op (per #collectFields call, per recompute,\n // per #listnavOptions) and NEVER stored on the instance — a morph replaces\n // nodes, so a cached predicate would close over stale roots.\n #ownershipFilter() {\n const nested = this.element.querySelectorAll('[data-controller~=\"reactive\"]')\n if (nested.length === 0) return () => true\n return (el) => this.#ownsField(el)\n }\n\n // #ownershipFilter for SERVER params (issue #410): nested client-only roots\n // are transparent, so a control under one is this root's when the first\n // token-bearing root above it is this.element. A nested token-bearing root\n // keeps its own fields (issue #15). Bindings, computes and listnav keep the\n // strict nearest-root #ownershipFilter.\n #serverFieldFilter() {\n const nested = this.element.querySelectorAll(AWAKE_ROOT)\n if (nested.length === 0 || [...nested].every(clientOnlyRoot)) return () => true\n return (el) => serverRoot(el, AWAKE_ROOT, this.element) === this.element\n }\n\n // One walk over THIS root's named controls (not a nested reactive root's),\n // returning both the scalar `fields` and any chosen `files`. The ownership\n // predicate is hoisted ONCE (issue #117) via #ownershipFilter — in the common\n // no-nested-root case it is a constant true, so we skip a closest() walk per\n // field. A file input's `.value` is the useless \"C:\\fakepath\\…\" string, never a\n // scalar — so its chosen files are collected separately (honoring `multiple`)\n // and it adds no phantom blank value (issue #34). An empty `files` keeps the\n // JSON path.\n #collectFields() {\n const fields = {}\n const files = []\n const owns = this.#serverFieldFilter() // compute ONCE per dispatch (issue #117)\n const controls = []\n this.element.querySelectorAll(\"input[name], select[name], textarea[name]\").forEach((field) => {\n if (!owns(field)) return\n if (field.type === \"file\") {\n // Carry the input's `multiple` flag so #buildFormData keeps the array\n // shape (params[name][]) even when the user picked exactly one file —\n // otherwise a [:file] schema would see a lone scalar upload and drop it.\n for (const file of field.files ?? []) files.push({ name: field.name, file, multiple: field.multiple })\n } else {\n // Held for a second pass: a hidden input is only a companion if a\n // checkbox somewhere in the root shares its name, which the first\n // occurrence cannot know yet.\n controls.push(field)\n }\n })\n // Collected before the first pass so the editor DOM is walked once.\n const editors = []\n this.element.querySelectorAll(`[name]${EDITOR_SELECTOR}`).forEach((el) => {\n if (owns(el)) editors.push(el) // the SAME hoisted predicate (nested reactive root, issue #15)\n })\n const arrayNames = this.#arrayFieldNames(controls)\n const companionNames = this.#companionNames(controls)\n for (const field of controls) {\n if (arrayNames.has(field.name)) {\n const slot = fields[field.name] ?? (fields[field.name] = [])\n if (field.type === \"checkbox\" || field.type === \"radio\") {\n // An unchecked box contributes NOTHING, the way a native submission\n // leaves it out. The group's value is the list of checked values, and\n // with none checked that list stays an EMPTY ARRAY rather than\n // vanishing: the action can tell \"the operator cleared them\" from\n // \"the group never rendered\", and an [:string] schema coerces [] to\n // []. A form body cannot carry the empty array, so #buildFormData\n // announces the group there instead.\n if (field.checked) slot.push(field.value)\n } else if (field.type === \"hidden\") {\n if (!companionNames.has(field.name)) slot.push(field.value)\n } else if (field.multiple && field.options) {\n for (const option of field.options) if (option.selected) slot.push(option.value)\n } else {\n slot.push(field.value)\n }\n } else if (field.type === \"checkbox\") {\n fields[field.name] = field.checked\n } else if (field.type === \"radio\") {\n if (field.checked) fields[field.name] = field.value\n } else {\n fields[field.name] = field.value\n }\n }\n // Named rich-text / custom editors (lexxy-editor, trix-editor) and bare\n // [contenteditable]. These aren't input/select/textarea, so the query above\n // skips them — without this, a reactive save posts an empty value and\n // silently wipes the field (issue #8). Read whatever the element exposes:\n // a custom editor's serialized `.value`, else its contenteditable text.\n // Under a plain name: only fill what the standard controls left absent or\n // empty, so a synced hidden input (e.g. Trix mirrors into one) still wins\n // when populated. Under a `[]` name the editor APPENDS to the group slot\n // instead, and a same-named hidden keeps its own say: nothing here can\n // tell a hidden that mirrors this editor from one that is a list JS\n // maintains.\n editors\n .forEach((el) => {\n // A plain element (e.g. a <div contenteditable>) has no `name` IDL\n // property — only the attribute — so read getAttribute, not el.name.\n const name = el.getAttribute(\"name\")\n if (!name) return\n const own = el.getAttribute(\"data-reactive-editable\") != null\n ? editableValue(el)\n : (el.value ?? el.textContent ?? el.innerHTML ?? \"\")\n // A `[]` name is a group here too: the editor APPENDS its value\n // instead of replacing the slot. Assigning a scalar posted\n // `{\"notes[]\": \"<p>x</p>\"}` while the draft snapshot pushed the same\n // control into an array — the wire and the draft disagreeing about one\n // field, and a declared array type seeing a string. A same-named hidden\n // is NOT read as this editor's twin: nothing here can tell a mirror\n // from a list JS maintains, and a value posted twice is visible while a\n // suppressed one is not.\n const existing = fields[name]\n // An editor that has not upgraded yet contributes NOTHING to a group.\n // Trix defines its elements in a setTimeout after load, and its \"\" is\n // not an empty value but an absent one: persistSnapshot omits such an\n // editor for the same reason, so this keeps the wire and the draft\n // saying the same thing.\n if (String(name).endsWith(\"[]\") && !collectorEditorReady(el)) return\n if (String(name).endsWith(\"[]\") && (existing === undefined || Array.isArray(existing))) {\n const slot = Array.isArray(existing) ? existing : (fields[name] = [])\n slot.push(own)\n return\n }\n // A scalar already under a `[]` name can only be a RADIO's: a radio\n // means \"pick one\" and keeps its single value with or without the\n // suffix, which is why #arrayFieldNames excepts it. Converting that to\n // a group here would discard the chosen value — measured, the post lost\n // it — so the editor stands down and the rule below applies, which\n // never overwrites a populated name.\n // Only fill what the standard controls left absent or empty, so a\n // synced hidden input still wins when populated.\n if (existing == null || existing === \"\") {\n fields[name] = own\n }\n })\n return { fields, files }\n }\n\n // Names collected as an ARRAY rather than a single value: a name carrying the\n // `[]` suffix, the HTML convention for a group. That suffix is the ONLY\n // trigger — a group says so, it is not inferred from two controls happening\n // to share a name. Radios are excluded BY DESIGN: a radio group shares one\n // name to mean \"pick one\", and it keeps posting the single checked value,\n // suffix or not.\n //\n // Issue #258: without this, `fields[name] = field.checked` wrote a boolean per\n // checkbox and same-named boxes overwrote each other, so three boxes with two\n // ticked left the browser as the LAST box's checked state — the chosen values\n // never reached the wire, whatever the action's schema declared.\n #arrayFieldNames(controls) {\n const names = new Set()\n for (const field of controls) {\n // A radio group shares one name BY DESIGN to mean \"pick one\" and keeps\n // its single checked value, `[]` suffix or not.\n if (field.type === \"radio\") continue\n if (String(field.name).endsWith(\"[]\")) names.add(field.name)\n }\n return names\n }\n\n // Names whose group carries a hidden COMPANION: a hidden input is Rails' way\n // of giving a checkbox a value for the unchecked case, and it is never a\n // chosen value. Measured from the helpers, the three shapes are:\n //\n // check_box(:u, :sub)\n // <input name=\"u[sub]\" type=\"hidden\" value=\"0\"><input type=\"checkbox\" value=\"1\" name=\"u[sub]\">\n // check_box(:u, :ids, {multiple: true}, \"3\")\n // <input name=\"u[ids][]\" type=\"hidden\" value=\"0\"><input type=\"checkbox\" value=\"3\" name=\"u[ids][]\">\n // collection_check_boxes(...) / an unchecked_value of nil\n // <input type=\"hidden\" name=\"u[ids][]\" value=\"\"> — or no hidden at all\n //\n // The value differs (unchecked_value, blank, absent), so the value cannot be\n // the test. What identifies a companion is that a checkbox shares its name.\n // A hidden WITHOUT a same-named checkbox is a list JS maintains, and its\n // value is a chosen value like any other.\n #companionNames(controls) {\n const names = new Set()\n for (const field of controls) if (field.type === \"checkbox\") names.add(field.name)\n return names\n }\n\n // Build the multipart body (issue #34). `token`/`act` are flat fields the\n // endpoint reads from params[:token]/params[:act]; scalar params nest under\n // params[<key>] (Rails parses the bracket into params[:params]); each file is\n // appended under params[<name>] (single) — a second file with the same name\n // (a `multiple` picker, several inputs sharing a name) is sent as\n // params[<name>][] so Rails coerces it to an array for a [:file] schema.\n #buildFormData(token, action, params, files) {\n const fd = new FormData()\n fd.append(\"token\", token)\n fd.append(\"act\", action)\n const emptyGroups = []\n for (const [key, value] of Object.entries(params)) {\n // A `[]` name carrying an array is the group shape (issue #258): every\n // element goes to params[name][], which Rack parses as an array. The\n // indexed form #appendField writes for a plain array (params[name][0],\n // params[name][1]) arrives as a hash of index keys — ParamSchema's array\n // type normalizes that back, but only an array type does, so the two\n // bodies would stop coercing identically for the same fields.\n //\n // An EMPTY group cannot be an empty array in a form body, so it is\n // ANNOUNCED instead: its key stays absent from params and its name goes\n // into `empty_groups[]`, a field of its own beside token/act/params. A\n // blank entry was the obvious alternative and is ambiguous — Rails leaves\n // `[\"\"]` to the caller, and a `[:date]` or `[:file]` element reads it as\n // \"did not come in\", so treating it as \"cleared\" would change what those\n // params mean. The field is additive: a server that ignores it behaves\n // exactly as it does today, and so does a client that never sends it.\n if (Array.isArray(value) && String(key).endsWith(\"[]\")) {\n if (value.length === 0) {\n emptyGroups.push(String(key).slice(0, -2))\n } else {\n const wire = `${this.#wireKey(key)}[]`\n for (const element of value) fd.append(wire, String(element))\n }\n } else {\n this.#appendField(fd, this.#wireKey(key), value)\n }\n }\n for (const name of emptyGroups) fd.append(\"empty_groups[]\", name)\n const multiNames = this.#multiFileNames(files)\n for (const { name, file, multiple } of files) {\n // params[name][] when the input is `multiple` (array shape even for one\n // file) OR the name repeats across inputs; otherwise a lone scalar file.\n const asArray = multiple || multiNames.has(name)\n const key = asArray ? `${this.#wireKey(name)}[]` : this.#wireKey(name)\n fd.append(key, file, file.name)\n }\n return fd\n }\n\n // The multipart wire key for a collected field/file name: params[...] with\n // the name's OWN brackets expanded into nesting segments (issue #231). The\n // old verbatim wrap of a Rails-bracketed name — params[blog_post[summary]] —\n // is unparseable by Rack: a scalar arrived as {\"blog_post[summary\" => {\"]\" =>\n // value}} (data corruption written through `update!` with a 200) and a file\n // never reached its schema key (silent drop). Expanding blog_post[summary]\n // into params[blog_post][summary] mirrors the server's bracket_path exactly —\n // split at the first \"[\", then each non-empty bracket segment; an empty\n // trailing segment (tags[]) drops, matching how the JSON path's\n // expand_bracket_keys coerces the same name — so a multipart body and a JSON\n // body coerce identically for the same fields (the documented contract).\n // A flat name stays a single params[name] wrap, byte-identical to before.\n #wireKey(name) {\n const raw = String(name)\n const head = raw.indexOf(\"[\")\n if (head === -1) return `params[${raw}]`\n const segments = [raw.slice(0, head), ...(raw.slice(head).match(/[^\\[\\]]+/g) ?? [])]\n return `params${segments.map((segment) => `[${segment}]`).join(\"\")}`\n }\n\n // Append a param leaf to FormData under its bracketed key. FormData carries\n // only strings, so a NON-scalar param (a nested object or an array) is\n // bracket-EXPANDED into params[key][sub] / params[key][index][...] fields —\n // the SAME Rails-form shape the server's expand_bracket_keys / array_values\n // already parse, so a JSON body and a multipart body coerce identically\n // (issue #39). Previously a non-scalar was JSON.stringify'd into one\n // params[key]='<json>' field, which the server received as an un-decodable\n // String leaf and DROPPED (nested hash -> {}, array -> key removed).\n //\n // Arrays use NUMERIC indices (params[key][0], params[key][1]) — required for\n // an array-of-hash so each element's sub-keys stay grouped and the server's\n // index-hash sort rebuilds order; params[key][] would collapse them. A scalar\n // (string/number/boolean) is one string field, mirroring the JSON wire shape\n // (the server's :boolean/:integer casts read \"true\"/\"42\"). null/undefined is\n // an empty field. An EMPTY array/object emits NOTHING — FormData can't carry\n // []/{}, so the key is omitted and the action's keyword default applies (this\n // differs from the JSON path, where an explicit [] coerces to an empty array).\n #appendField(fd, key, value) {\n if (value == null) {\n fd.append(key, \"\")\n } else if (Array.isArray(value)) {\n value.forEach((element, index) => this.#appendField(fd, `${key}[${index}]`, element))\n } else if (typeof value === \"object\") {\n for (const [subKey, subValue] of Object.entries(value)) {\n this.#appendField(fd, `${key}[${subKey}]`, subValue)\n }\n } else {\n fd.append(key, String(value))\n }\n }\n\n // Names that appear more than once across the chosen files (a `multiple`\n // picker, or several file inputs sharing a name) — those go to params[name][]\n // so the server sees an array; a lone file stays params[name].\n #multiFileNames(files) {\n const counts = new Map()\n for (const { name } of files) counts.set(name, (counts.get(name) ?? 0) + 1)\n return new Set([...counts].filter(([, n]) => n > 1).map(([name]) => name))\n }\n\n #parseParams(raw) {\n if (!raw) return {}\n try {\n return typeof raw === \"string\" ? JSON.parse(raw) : raw\n } catch {\n return {}\n }\n }\n\n // Stimulus typecasts a JSON param to the parsed array already; a raw string\n // (hand-built attr, non-typecasting harness) is parsed here. Delegates to the\n // shared parseOps so the controller and the reactive:js stream action (issue\n // #97) treat a malformed ops attr identically (→ [], never a throw).\n #parseOps(raw) {\n return parseOps(raw)\n }\n\n // Interpret a [[name, args], ...] op list against this root (issue #95),\n // scoping each op's targets to this controller's own root via #opTargets (the\n // nested-reactive-root ownership filter, issue #15). The whitelist + skip\n // logic lives in the shared applyOps so runOps and the reactive:js stream\n // action interpret the SAME vocabulary the SAME way (client-side default-deny).\n #applyOps(list) {\n applyOps(\n list,\n (args) => this.#opTargets(args),\n (name, args) => this.#diagnoseZeroTargets(`client op \"${name}\"`, args),\n )\n }\n\n // Issue #237: the verbose gate for zero-target diagnostics — the\n // verbose_errors stamp (ON by default in dev/test) or full debug mode (a\n // debug user must never see less). Read live off the root like #debugEnabled.\n #verboseEnabled() {\n return this.element?.getAttribute?.(\"data-reactive-verbose\") === \"true\" || this.#debugEnabled()\n }\n\n // Issue #237: called when a selector-form target resolved to ZERO elements on\n // this root. Past the verbose gate the devtools feature (issue #275) does the\n // work: the DOM probes, the dedupe, the trap-specific hint.\n #diagnoseZeroTargets(label, args) {\n if (!this.#verboseEnabled()) return\n if (__SPLIT__) this.#devtools((devtools) => devtools.diagnose(this, label, args))\n else devtoolsFeature.diagnose(this, label, args)\n }\n\n // Run `use` with the devtools feature: now when it is loaded (the default\n // client, always), else once it has arrived — a warning or a trace a tick\n // late is still a warning. Called only past a debug/verbose gate, so a page\n // with neither never asks for the module.\n #devtools(use) {\n if (__SPLIT__) withFeature(\"devtools\", use)\n }\n\n // Resolve an op's targets: \"@root\" is this element; a selector resolves\n // WITHIN this root and excludes nested reactive roots' subtrees (issue #15\n // semantics — the same nearest-root ownership check the field walk uses);\n // global: true opts a single op out to document-wide.\n #opTargets(args) {\n const to = args.to\n if (to === \"@root\") return [this.element]\n if (typeof to !== \"string\" || to === \"\") return []\n if (args.global) return [...document.querySelectorAll(to)]\n return [...this.element.querySelectorAll(to)].filter((el) => this.#ownsField(el))\n }\n\n // Whether a click-bound checkbox/radio trigger should keep its NATIVE flip\n // (issue #98). `checked: :keep` means \"let the control flip now\": the\n // unconditional preventDefault is exactly what suppresses that flip until the\n // morph, so we skip it — but ONLY for a checkbox/radio (a bare toggle click\n // has no form-navigation default to lose). Any other element (a button) keeps\n // preventDefault so an in-form submit can't navigate.\n #keepsNativeToggle(optimistic, target) {\n if (optimistic?.checked !== \"keep\") return false\n const type = target?.type\n return type === \"checkbox\" || type === \"radio\"\n }\n\n // Replay the recorded undo ops on failure (issue #98), guarded by isConnected:\n // a plain (non-morph) replace can detach this subtree before the failure lands,\n // and reverting a stale/detached node is pointless (it's gone) — so a\n // disconnected root skips the revert entirely. On success NOTHING calls this:\n // the server re-render overwrites the hint, or (reply.remove / streams-only)\n // the hint is deliberately left standing.\n #revertOptimistic(inverse) {\n if (!inverse) return\n if (!this.element.isConnected) return\n for (const undo of inverse) undo()\n }\n\n // Apply the BUSY state for THIS enqueue (issue #181) and return a `settle`\n // closure that undoes exactly this enqueue's contribution when the round trip\n // finishes (success OR any failure). Everything is refcounted so overlapping\n // enqueues never clobber: A's settle can't clear busy while B is still pending.\n //\n // Two layers:\n // 1. The ALWAYS-ON busy vocabulary (fires for every action, no hint needed):\n // data-reactive-busy=\"<action>\" on the trigger and the root (a\n // space-separated, per-action refcounted set), aria-busy on the root (a\n // pending counter), and data-reactive-busy on any busy_on element scoped\n // to this action. Apps style a spinner with pure CSS and zero Ruby.\n // 2. The busy HINT (only when busy: was declared): the SAME cosmetic op set\n // as optimistic: (class ops, hide/show, disable, text), applied by the\n // hints feature (features/hints.js) and reverted on SETTLE (not on\n // failure) — its undo ops land in `pending.undo`, which the settle\n // closure replays. They apply at ENQUEUE — never during a debounce quiet\n // period — so a debounced input is not disabled mid-typing.\n #applyBusy(action, trigger, pending) {\n this.#markBusy(action, trigger)\n // Global activity signal (issue #201): this enqueue is one in-flight reactive\n // operation. Entered HERE (at enqueue, via #applyBusy) so the document marker\n // covers the whole pending window — queue wait included — exactly like the\n // per-root busy vocabulary. Exited once in the settle closure below, which\n // #perform runs in its finally on EVERY exit path (success or any failure).\n enterReactiveActivity()\n\n let settled = false\n return () => {\n if (settled) return // one settle per enqueue, even if called twice\n settled = true\n this.#unmarkBusy(action, trigger)\n exitReactiveActivity()\n // Busy reverts on SETTLE regardless of outcome, guarded per element.\n for (const op of pending.undo) op()\n }\n }\n\n // Layer 1 — the always-on busy markers. Trigger + root carry the action token;\n // the root's counter drives aria-busy; busy_on elements scoped to this action\n // light up. Refcounts (#busyActions, #busyPending) so overlapping requests\n // don't clear each other.\n #markBusy(action, trigger) {\n this.#setBusyToken(trigger, action, +1)\n this.#setBusyToken(this.element, action, +1)\n\n this.#busyActions.set(action, (this.#busyActions.get(action) ?? 0) + 1)\n if (this.#busyPending++ === 0) this.element.setAttribute(\"aria-busy\", \"true\")\n\n for (const el of this.#busyOnTargets(action)) this.#setBusyToken(el, action, +1)\n }\n\n #unmarkBusy(action, trigger) {\n this.#setBusyToken(trigger, action, -1)\n this.#setBusyToken(this.element, action, -1)\n\n const count = (this.#busyActions.get(action) ?? 1) - 1\n if (count <= 0) this.#busyActions.delete(action)\n else this.#busyActions.set(action, count)\n\n if (--this.#busyPending <= 0) {\n this.#busyPending = 0\n this.element.removeAttribute(\"aria-busy\")\n }\n\n for (const el of this.#busyOnTargets(action)) this.#setBusyToken(el, action, -1)\n }\n\n // Add (+1) or remove (-1) `action` from an element's space-separated\n // data-reactive-busy token set, refcounted PER ELEMENT+ACTION so two queued\n // requests of the same action on the same element don't drop the token early\n // (and two DIFFERENT actions both keep their token — the set never clobbers).\n // The attribute is removed only when the set empties. No-op on a nullish/\n // detached element (a morph may have replaced the trigger before settle).\n #setBusyToken(el, action, delta) {\n if (!el || typeof el.getAttribute !== \"function\") return\n\n const counts = (this.#busyTokenCounts.get(el) ?? new Map())\n const next = (counts.get(action) ?? 0) + delta\n if (next <= 0) counts.delete(action)\n else counts.set(action, next)\n\n if (counts.size === 0) {\n this.#busyTokenCounts.delete(el)\n el.removeAttribute(\"data-reactive-busy\")\n return\n }\n this.#busyTokenCounts.set(el, counts)\n el.setAttribute(\"data-reactive-busy\", [...counts.keys()].join(\" \"))\n }\n\n // busy_on elements scoped to THIS action, owned by this root (not a nested\n // reactive root's, issue #15). data-reactive-busy-on=\"<action>\" is the marker\n // busy_on(:action) emits.\n #busyOnTargets(action) {\n const nodes = this.element.querySelectorAll?.(\"[data-reactive-busy-on]\") ?? []\n return [...nodes].filter(\n (el) => el.getAttribute(\"data-reactive-busy-on\") === action && this.#ownsField(el),\n )\n }\n\n // The action path comes from a <meta> tag that is fixed for the page's life,\n // so resolve it once per controller and cache it — avoids a querySelector on\n // every dispatch (this runs on the request hot path, once per click/keystroke\n // round trip). Cached on the instance, so a fresh connect() (after a Turbo\n // navigation swaps the element) re-reads it.\n #actionPath() {\n return (this.#actionPathCache ??=\n document.querySelector('meta[name=\"phlex-reactive-action-path\"]')?.content ||\n \"/reactive/actions\")\n }\n\n // The per-request timeout in ms (issue #101), from a page-stable\n // <meta name=\"phlex-reactive-timeout\"> (default 30000). Cached per-controller\n // like the action path. Parsed defensively: a missing/blank/non-positive/NaN\n // value falls back to the default, so a typo'd meta can never disable the\n // timeout (which would reintroduce the wedged-queue bug) or set a zero/negative\n // window that aborts instantly.\n #timeoutMs() {\n if (this.#timeoutMsCache != null) return this.#timeoutMsCache\n const raw = document.querySelector('meta[name=\"phlex-reactive-timeout\"]')?.content\n const ms = Number(raw)\n return (this.#timeoutMsCache = Number.isFinite(ms) && ms > 0 ? ms : 30000)\n }\n\n // CSRF token and connection id are read LIVE (not cached) on purpose: Rails\n // can rotate the CSRF token, and the pgbus connection id changes on an SSE\n // reconnect — caching either would send a stale value. A single querySelector\n // per request is cheap next to the round trip itself.\n #csrfToken() {\n return document.querySelector('meta[name=\"csrf-token\"]')?.content ?? \"\"\n }\n\n // The pgbus SSE connection id, if the page is subscribed to a stream. pgbus\n // reflects it onto the <pgbus-stream-source connection-id=\"…\"> element (and\n // apps may mirror it to <meta name=\"pgbus-connection-id\">). Returns null\n // when not present (e.g. no pgbus, or no active subscription) — the header\n // is then simply omitted.\n #connectionId() {\n return (\n document.querySelector(\"pgbus-stream-source[connection-id]\")?.getAttribute(\"connection-id\") ||\n document.querySelector('meta[name=\"pgbus-connection-id\"]')?.content ||\n null\n )\n }\n}\n\n// Register with Turbo as soon as it is there. LAST in the file: registration\n// reads the feature table (the dev module is loaded from it), which must\n// exist by then.\nif (typeof window !== \"undefined\") {\n if (window.Turbo) registerReactiveActions()\n else document.addEventListener(\"turbo:load\", registerReactiveActions, { once: true })\n}\n",
|
|
6
|
-
"// phlex/reactive/core — the OPT-IN client: the reactive controller without\n// its feature modules, which it imports when a root on the page needs one\n// (issue #275). The default is phlex/reactive/reactive_controller: one file\n// with every feature inside, nothing imported later. An app imports ONE of\n// the two, never both.\n//\n// The controller itself is the shared runtime (runtime.js); the shipped\n// core.min.js is this file BUNDLED with it. All this entry adds is where each\n// feature module lives: one LITERAL import() per feature, by the bare\n// specifier the engine pins (phlex/reactive/features/<name>) — literal so a\n// bundler can see it and split the feature into its own chunk, bare so the\n// import map resolves it to that module's own digested file (issue #57).\n//\n// What the opt-in costs a page is documented in the README (\"The split\n// client\"): the first root that needs a feature waits for its import.\nimport ReactiveController, {\n loadReactiveFeature,\n onReactiveStreamWithoutEffects,\n registerReactiveDev,\n registerReactiveFeatureLoader,\n unloadedFeaturesFor,\n} from \"phlex/reactive/runtime\"\n\nregisterReactiveFeatureLoader(\"persist\", () => import(\"phlex/reactive/features/persist\"), waitingForPersist)\nregisterReactiveFeatureLoader(\"defer\", () => import(\"phlex/reactive/features/defer\"))\nregisterReactiveFeatureLoader(\"form\", () => import(\"phlex/reactive/features/form\"))\nregisterReactiveFeatureLoader(\"bindings\", () => import(\"phlex/reactive/features/bindings\"))\nregisterReactiveFeatureLoader(\"compute\", () => import(\"phlex/reactive/features/compute\"), waitingForEdit)\nregisterReactiveFeatureLoader(\"sortable\", () => import(\"phlex/reactive/features/sortable\"))\nregisterReactiveFeatureLoader(\"presence\", () => import(\"phlex/reactive/features/presence\"))\nregisterReactiveFeatureLoader(\"countdown\", () => import(\"phlex/reactive/features/countdown\"))\nregisterReactiveFeatureLoader(\"effects\", () => import(\"phlex/reactive/features/effects\"))\nregisterReactiveFeatureLoader(\"hints\", () => import(\"phlex/reactive/features/hints\"))\nregisterReactiveFeatureLoader(\"devtools\", () => import(\"phlex/reactive/features/devtools\"))\n// The runtime registered with Turbo before this file ran: now it knows where\n// the dev module is, a development page can have it.\nregisterReactiveDev()\n\n// --- While the persist module is on its way ---------------------------------------\n// What the draft code must not miss while it is on its way: that the\n// user edited a field (the edit must be drafted once the module is here)\n// and that the form around the root was submitted successfully (the\n// draft must be forgotten, not restored). The feature listens for both\n// itself once it has connected.\nfunction waitingForPersist(root, pending) {\n const edited = () => {\n pending.edited = true\n }\n const submitted = (event) => {\n const form = event.target\n if (event.detail?.success && form?.tagName === \"FORM\" && form.contains?.(root)) pending.submitted = true\n }\n // (Lexical and Trix swallow the native `input` of their editor; their\n // own bubbling change events are the keystroke signal.)\n const edits = [\"input\", \"change\", \"lexxy:change\", \"trix-change\"]\n for (const type of edits) root.addEventListener?.(type, edited)\n document.addEventListener(\"turbo:submit-end\", submitted)\n return () => {\n for (const type of edits) root.removeEventListener?.(type, edited)\n document.removeEventListener(\"turbo:submit-end\", submitted)\n }\n}\n\n// --- While the compute module is on its way --------------------------------------\n// An edit the user made before the module could recompute: it runs one pass\n// when it connects, over the fields as the user left them.\nfunction waitingForEdit(root, pending) {\n const edited = () => {\n pending.edited = true\n }\n root.addEventListener?.(\"input\", edited)\n root.addEventListener?.(\"change\", edited)\n return () => {\n root.removeEventListener?.(\"input\", edited)\n root.removeEventListener?.(\"change\", edited)\n }\n}\n\n// --- A stream that needs a module before it is loaded ------------------------------\n// A stream can bring the page's first use of a feature: a broadcast appends\n// the first dismissing flash, a reply removes a row with an exit effect or\n// swaps in a form with show bindings and tag chips. Stream effects are\n// document-level (features/effects.js) and an exit effect runs BEFORE the\n// render; a swapped-in root connects AFTER it and would start its import\n// then, showing the server's rendering until the module arrives. Both are\n// avoided the same way: the render WAITS for the imports (`hold`), so the\n// stream is animated and the new root connects with its modules present.\n// Which modules a stream needs is read off the runtime's feature table\n// (unloadedFeaturesFor: every incoming reactive root's marker checks), plus\n// the effects module's own stream-level checks — no per-feature copy here.\n//\n// Turbo renders each <turbo-stream> on its own, so one waiting render would\n// let a later stream to the same target overtake it: while the hold is open\n// EVERY stream's render waits on the same promise, and so they resume in\n// arrival order. The hold is an animation's worth of patience, never the\n// import timeout: after STREAM_HOLD_MS the streams render plain, a root\n// connects and imports as usual, and the effects module, when it does arrive,\n// sweeps up the flashes it missed (their timers then start at arrival, not at\n// render). A module already loaded is never waited for.\nconst STREAM_HOLD_MS = 1000\nlet hold = null\nlet effects\nconst holding = new Set() // the modules the open hold is waiting for\n\nfunction loadIntoHold(name) {\n return loadReactiveFeature(name).then((feature) => {\n if (name !== \"effects\") return\n effects = feature\n feature?.sweep()\n })\n}\n\nonReactiveStreamWithoutEffects((event, loadedEffects) => {\n const detail = event.detail\n const render = detail?.render\n if (typeof render !== \"function\") return\n const streamEl = detail.newStream ?? event.target\n const names = unloadedFeaturesFor(incomingRoots(streamEl)).filter((name) => !holding.has(name))\n if (!loadedEffects && !holding.has(\"effects\") && streamNeedsEffects(streamEl)) names.push(\"effects\")\n // Nothing to wait for, and no hold open: a loaded effects module wraps the\n // stream now, as it would without this entry.\n if (names.length === 0 && !hold) return loadedEffects?.wrap(event)\n // A hold that is open grows: a later stream's modules join it (the hold\n // then lasts up to another STREAM_HOLD_MS from now), so a second stream is\n // never left to render without the module it asked for.\n if (names.length > 0) {\n for (const name of names) holding.add(name)\n const opened = (hold = Promise.race([\n Promise.all([hold, ...names.map(loadIntoHold)]),\n new Promise((resolve) => setTimeout(resolve, STREAM_HOLD_MS)),\n ]).then(() => {\n if (hold !== opened) return\n hold = null\n holding.clear()\n }))\n }\n const waiting = hold\n detail.render = async (streamElement) => {\n await waiting\n // Wrapped now, not when the event fired: nothing has rendered meanwhile.\n // On a copy: the event's detail.render is not ours to set back — a defer\n // reply has already rendered it, and leaves Turbo a no-op (issue #336).\n const held = { target: event.target, detail: { newStream: detail.newStream, render } }\n ;(loadedEffects ?? effects)?.wrap(held)\n await held.detail.render(streamElement)\n }\n})\n\n// The reactive roots a stream is about to put on the page: the roots in its\n// template (the template's own root element included), and the content\n// itself — read as part of the root already on the page it renders into,\n// which re-reads its markers once the render has run (issue #356; an editable\n// it brings, #351). Held for the module, the new content lands wired.\nfunction incomingRoots(streamEl) {\n const content = streamEl?.querySelector?.(\"template\")?.content\n if (!content) return []\n const roots = [...(content.querySelectorAll?.('[data-controller~=\"reactive\"]') ?? [])]\n for (const child of content.children ?? []) {\n if (child.matches?.('[data-controller~=\"reactive\"]') && !roots.includes(child)) roots.unshift(child)\n }\n roots.push(content)\n return roots\n}\n\n// Does this stream need the effects module? A cheap superset of what the\n// module itself resolves: a per-call effect, an effect declared on the target\n// or on incoming content, a dismissing flash arriving or already on the page.\nfunction streamNeedsEffects(streamEl) {\n return Boolean(\n streamEl?.hasAttribute?.(\"data-reactive-effect\") ||\n document\n .getElementById?.(streamEl?.getAttribute?.(\"target\"))\n ?.matches?.(\"[data-reactive-effect-update],[data-reactive-effect-exit]\") ||\n streamEl\n ?.querySelector?.(\"template\")\n ?.content?.querySelector?.(\"[data-reactive-effect-enter],[data-reactive-dismiss-after]\") ||\n document.querySelector?.(\"[data-reactive-dismiss-after]:not([data-reactive-dismiss-scheduled])\"),\n )\n}\n\n\nexport * from \"phlex/reactive/runtime\"\nexport default ReactiveController\n"
|
|
5
|
+
"import { Controller } from \"@hotwired/stimulus\"\n// Import the BARE specifier the engine already pins (phlex/reactive/confirm),\n// NOT a relative \"./confirm.js\" (issue #57). Under importmap-rails + Propshaft\n// the controller is served at its DIGESTED url; a relative sibling import is\n// left untouched (Propshaft rewrites only RAILS_ASSET_URL(...), and the import\n// map resolves ONLY bare specifiers), so \"./confirm.js\" resolves against the\n// digested controller url → an undigested /assets/.../confirm.js that 404s, and\n// the throwing import takes down every Stimulus controller on the page. The\n// bare specifier resolves to the digested asset through the import map, and\n// bundlers/bun resolve it the same way they already resolve\n// \"phlex/reactive/reactive_controller\" (see tsconfig.json paths for the tests).\nimport { confirmResolver } from \"phlex/reactive/confirm\"\n// The feature modules, imported STATICALLY (issue #305). Only the default\n// entry uses these: there `__SPLIT__` is false (scripts/build_client.js\n// defines it), every reference below sits in a `__SPLIT__ ? … : …` branch the\n// minifier keeps, and the features end up in the same file as the runtime,\n// called directly. The opt-in entry (core) is built with `__SPLIT__` true:\n// those branches fold away, and its build resolves these five imports to an\n// empty module, so core.min.js imports no feature until a root needs one.\n//\n// THE ORDER BELOW IS THE DEFAULT BUNDLE'S LAYOUT, not the connect order (that\n// is #connectFeaturesNow's, persist first). The bundler lays the features out\n// in import order, ahead of this file's own code, and gzip only matches text\n// within 32 KB: a feature that repeats what the runtime also says (defer's\n// fetch headers and meta reads, persist's editor selector, form's dirty\n// marker) compresses against it only when it sits close to the runtime. This\n// order was the smallest of a swap search over all of them (issue #310):\n// ~450 B gzipped less than the table order. None of the features runs\n// anything at import time, so the order changes bytes, never behaviour.\nimport * as bindingsFeature from \"phlex/reactive/features/bindings\"\nimport * as computeFeature from \"phlex/reactive/features/compute\"\nimport * as persistFeature from \"phlex/reactive/features/persist\"\nimport * as formFeature from \"phlex/reactive/features/form\"\nimport * as effectsFeature from \"phlex/reactive/features/effects\"\nimport * as devtoolsFeature from \"phlex/reactive/features/devtools\"\nimport * as hintsFeature from \"phlex/reactive/features/hints\"\nimport * as deferFeature from \"phlex/reactive/features/defer\"\nimport * as sortableFeature from \"phlex/reactive/features/sortable\"\nimport * as dragFeature from \"phlex/reactive/features/drag\"\nimport * as presenceFeature from \"phlex/reactive/features/presence\"\nimport * as countdownFeature from \"phlex/reactive/features/countdown\"\n\n// phlex/reactive/runtime — the reactive controller WITHOUT its feature\n// modules (issue #275): the code both client entries share. It is not an\n// entry itself and is never pinned; each entry is built with this file inside\n// it. An app reaches it one of two ways, never both:\n//\n// phlex/reactive/reactive_controller the default: this runtime bundled with\n// every feature in one file, built with\n// __SPLIT__ false (issue #305): the\n// features are imported statically and\n// called directly, and connect inside\n// connect(). It has no table and NO way\n// to import one.\n// phlex/reactive/core opt-in: this runtime plus the table of\n// import() calls (core.js). A feature is\n// imported the first time a root on the\n// page needs it (see \"Feature modules\").\n//\n// The ONE generic controller behind every reactive Phlex component. It\n// replaces the per-feature Stimulus controllers you'd otherwise hand-write\n// for interactive components. A component declares its actions in Ruby (via\n// Phlex::Reactive::Component); this controller binds DOM events to a single\n// HTTP round trip and lets Turbo apply the re-rendered component back in\n// (replace by default; method=\"morph\" — Response.morph — preserves focus).\n//\n// Wire format (client -> server), POST <action path>, turbo-stream Accept:\n// { token: \"<signed identity>\", act: \"<action>\", params: {...} } (JSON)\n// (`act`, not `action`: `action` is a reserved Rails routing param.)\n// The token is a MessageVerifier-signed { component, gid } — NO state is sent.\n// When the root holds a chosen <input type=\"file\">, the SAME payload is sent as\n// multipart FormData instead (token/act flat, params bracketed, files appended)\n// so an upload reaches the action (issue #34) — only the encoding differs.\n// The response is a <turbo-stream> that replaces the component by its id.\n//\n// Server -> client live updates use the SAME element id, pushed over the\n// stream transport (pgbus SSE / Action Cable) via the Streamable\n// .broadcast_* methods — so a click and a background broadcast converge on\n// one re-render unit.\n//\n// Custom turbo-stream action: the server tells the actor to full-navigate\n// (e.g. the record's slug changed and the current URL is now dead). It rides a\n// 200 turbo-stream — NOT an HTTP 3xx — so it never trips the response.redirected\n// bail below (which still correctly catches real auth/CSRF redirects). Registered\n// once on the Turbo global (no @hotwired/turbo import — the gem uses window.Turbo\n// everywhere, and a named import is unreliable under importmap/esbuild).\nexport function registerReactiveVisit() {\n const actions = window.Turbo?.StreamActions\n if (!actions || actions[\"reactive:visit\"]) return\n actions[\"reactive:visit\"] = function () {\n const url = this.getAttribute(\"data-url\")\n if (url) window.Turbo.visit(url, { action: \"advance\" })\n }\n}\n\n// Custom turbo-stream action: a TOKEN-ONLY refresh (issue #30). A partial\n// update (Response.streams / reply.streams) re-renders only PART of a component\n// — so there's no full-self replace to carry the next signed token. The server\n// instead emits `<turbo-stream action=\"reactive:token\" target=\"<id>\"\n// data-reactive-token-value=\"<fresh>\">`. #perform's #extractToken already reads\n// the token out of the response body for the NEXT queued request; this handler\n// keeps the DOM in sync too, writing the attribute onto the root element so the\n// `tokenValue` fallback stays fresh. It's a pure attribute set — no node is\n// replaced — so a focused <input> + caret survive (the whole point: update a\n// total cell without tearing down the field the user is typing in).\nexport function registerReactiveToken() {\n const actions = window.Turbo?.StreamActions\n if (!actions || actions[\"reactive:token\"]) return\n actions[\"reactive:token\"] = function () {\n const token = this.getAttribute(\"data-reactive-token-value\")\n const target = this.getAttribute(\"target\")\n if (!token || !target) return\n const el = document.getElementById(target)\n // Stimulus reads the token via the `token` value -> data-reactive-token-value.\n if (el) el.setAttribute(\"data-reactive-token-value\", token)\n }\n}\n\n// Custom turbo-stream action: SERVER-PUSHED client DOM ops (issue #97). The\n// server-side sibling of on_client's runOps — a reply (reply.<verb>.js(ops)) or\n// a broadcast (Streamable.broadcast_js_to) emits\n//\n// <turbo-stream action=\"reactive:js\" target=\"<optional root id>\"\n// data-reactive-ops=\"[[op, args], ...]\"></turbo-stream>\n//\n// and Turbo invokes this handler with `this` bound to that <turbo-stream>\n// element. It runs the ops through the SAME frozen CLIENT_OPS whitelist as\n// runOps (client-side default-deny — an unknown op warns + is skipped), so a\n// forged/stale ops attr can never break the page or execute anything off the\n// vocabulary. NO token, NO fetch — a pure local DOM mutation.\n//\n// `target` (optional, an element id) scopes op resolution to that root: \"@root\"\n// resolves to the target element itself and a selector resolves WITHIN it.\n// Without a target, ops resolve document-wide (a broadcast op like\n// add_class(\"#bell\", ...) that isn't anchored to one component). The op stream\n// is emitted AFTER all render streams in the reply (the endpoint appends it\n// last), so focus(\"[name=next]\") sees the freshly morphed DOM — Turbo applies\n// streams in document order.\nexport function registerReactiveJs() {\n const actions = window.Turbo?.StreamActions\n if (!actions || actions[\"reactive:js\"]) return\n actions[\"reactive:js\"] = function () {\n const list = parseOps(this.getAttribute(\"data-reactive-ops\"))\n if (!list.length) return\n const targetId = this.getAttribute(\"target\")\n // Issue #237: the server stamps the verbose gate on the stream element\n // itself (verbose_errors), so document-scoped ops are diagnosable too.\n const verbose = this.getAttribute(\"data-reactive-verbose\") === \"true\"\n // With a target: scope to that element (missing → no-op). Without: document.\n const root = targetId ? document.getElementById(targetId) : null\n if (targetId && !root) {\n if (verbose) {\n if (__SPLIT__) withFeature(\"devtools\", (devtools) => devtools.missingRoot(targetId))\n else devtoolsFeature.missingRoot(targetId)\n }\n return\n }\n applyOps(\n list,\n (args) => streamOpTargets(args, root),\n verbose\n ? (name, args) =>\n __SPLIT__\n ? withFeature(\"devtools\", (devtools) => devtools.diagnoseStream(name, args, root))\n : devtoolsFeature.diagnoseStream(name, args, root)\n : undefined,\n )\n }\n}\n\n// --- Deferred and lazy renders (issues #165, #276, #277) ---------------------\n// reply.defer, reactive_lazy and reactive_lazy(on:/cache:) live in the defer\n// feature module (issue #275, features/defer.js). The core keeps only the\n// `reactive:defer` stream action's REGISTRATION: such a stream can reach a\n// page that has not loaded the module yet (the first reply.defer on a page\n// with no lazy root), and it must not be lost. The <turbo-stream> element is\n// handed over once the module is here — at once when it already is.\nexport function registerReactiveDefer() {\n const actions = window.Turbo?.StreamActions\n if (!actions || actions[\"reactive:defer\"]) return\n actions[\"reactive:defer\"] = function () {\n if (__SPLIT__) withFeature(\"defer\", (defer) => defer.streamAction(this))\n else deferFeature.streamAction(this)\n }\n}\n\n// --- Around a stream render: effects and dismissing flashes (issues #100, #215) ---\n// Both live in the effects feature module (issue #275, features/effects.js).\n// The runtime keeps the ONE document-level turbo:before-stream-render listener\n// (it fires for every <turbo-stream>: a reply AND a broadcast) and hands each\n// event to the module, which wraps event.detail.render.\n//\n// In the default client the module is always here. The opt-in client may not\n// have imported it yet when a stream first needs it; what happens then is that\n// entry's business (core.js installs it: the stream's render waits for the\n// import). The runtime only offers the event.\nlet streamRenderRegistered = false\nlet streamWithoutEffects = null\n\nexport function registerReactiveStreamRender() {\n if (streamRenderRegistered) return\n if (typeof document === \"undefined\" || typeof document.addEventListener !== \"function\") return\n streamRenderRegistered = true\n document.addEventListener(\"turbo:before-stream-render\", (event) => {\n streamOwnReply(event)\n streamIntoRoots(event)\n streamModals(event)\n // The opt-in entry sees every stream (it may hold one for a module the\n // incoming content needs); with no entry hook, a loaded effects module\n // wraps the stream directly — in the default entry, always.\n if (!__SPLIT__) effectsFeature.wrap(event)\n else if (streamWithoutEffects) streamWithoutEffects(event, featureModules.get(\"effects\"))\n else featureModules.get(\"effects\")?.wrap(event)\n })\n}\n// The names this registration had while it was two listeners.\nexport { registerReactiveStreamRender as registerReactiveDismiss, registerReactiveStreamRender as registerReactiveEffects }\n\n// --- A stream into a connected root (issue #356) ---\n// A morph of a root makes it re-read its feature markers (#boundRootMorph); a\n// stream that renders INTO it — append, prepend or update of the root or a\n// child, before, after or replace of a child — fires neither a lifecycle nor\n// a morph of the root, so a marker it brings was never connected. The roots\n// a stream renders into are resolved here, before anything (effects, the opt-in\n// entry's hold) wraps Turbo's render; once that render has run, each one still\n// connected is told (STREAMED, not bubbling: an outer root's content is not\n// its own). The root re-runs its connected features' seeds and connects what\n// the new content needs, in both entries.\nconst STREAMED = \"reactive:streamed\"\n\nfunction streamIntoRoots(event) {\n const detail = event.detail\n const render = detail?.render\n const stream = detail?.newStream ?? event.target\n const action = stream?.getAttribute?.(\"action\")\n const into = /^(append|prepend|update)$/.test(action)\n if (typeof render !== \"function\" || !(into || /^(before|after|replace)$/.test(action))) return\n const id = stream.getAttribute(\"target\")\n const selector = stream.getAttribute(\"targets\")\n const roots = new Set()\n for (const target of id ? [document.getElementById(id)] : selector ? document.querySelectorAll(selector) : []) {\n const root = (into ? target : target?.parentElement)?.closest?.('[data-controller~=\"reactive\"]')\n if (root) roots.add(root)\n }\n if (roots.size === 0) return\n detail.render = async (el) => {\n await render(el)\n for (const root of roots) if (root.isConnected) root.dispatchEvent(new Event(STREAMED))\n }\n}\n\n// --- Modal dialogs (issue #365) ---\n// show_modal / close_dialog reach the browser's own modal dialog: the top\n// layer, the focus trap, ::backdrop, Escape and focus restored to the opener.\n// A dialog the SERVER renders open with data-reactive-modal becomes modal as\n// soon as the client sees it: at a root's connect (a full page, a replaced\n// root), after a morph of a root or of the page, and after a stream whose\n// content holds a dialog. Duck-typed on tagName (the submitFormFor precedent:\n// fake-node and test friendly).\nfunction isDialog(el) {\n return el?.tagName === \"DIALOG\" && typeof el.showModal === \"function\"\n}\n\n// `:modal` is the only way to tell a modal dialog from one opened with the\n// `open` attribute. An engine that does not know the pseudo-class throws a\n// SyntaxError; it then counts as not modal (show_modal reopens it modally).\nfunction isModal(dialog) {\n try {\n return dialog.matches(\":modal\")\n } catch {\n return false\n }\n}\n\nfunction showModal(el) {\n if (!isDialog(el)) {\n // Said where a zero-target op is (issue #237): under verbose_errors or debug.\n if (el?.closest?.('[data-reactive-verbose=\"true\"], [data-reactive-debug=\"true\"]')) {\n console.warn(`[phlex-reactive] show_modal targets a <dialog>, not <${el.localName}> — skipped`)\n }\n return\n }\n if (!el.isConnected || isModal(el)) return\n // showModal() throws on a dialog already open non-modally: close it first,\n // in the same task, so nothing paints in between.\n if (el.open) el.close()\n el.showModal()\n}\n\nfunction closeDialog(el) {\n if (isDialog(el) && el.open) el.close()\n}\n\n// Make every open dialog[data-reactive-modal] in `scope` modal — `scope` itself\n// too, for a root that is the dialog.\nfunction upgradeModals(scope) {\n const sel = \"dialog[data-reactive-modal]\"\n const dialogs = [...(scope?.querySelectorAll?.(sel) ?? [])]\n if (scope?.matches?.(sel)) dialogs.push(scope)\n for (const dialog of dialogs) {\n if (dialog.open && !isModal(dialog)) showModal(dialog)\n }\n}\n\n// After a stream whose content holds a dialog has rendered, upgrade the page's\n// marked dialogs. Document-wide: a replace's target is gone by then.\nfunction streamModals(event) {\n const detail = event.detail\n const render = detail?.render\n const stream = detail?.newStream ?? event.target\n if (typeof render !== \"function\" || !stream?.querySelector?.(\"template\")?.content?.querySelector?.(\"dialog\")) return\n detail.render = async (el) => {\n await render(el)\n upgradeModals(document)\n }\n}\n\nlet modalsRegistered = false\n\nexport function registerReactiveModals() {\n if (modalsRegistered) return\n if (typeof document === \"undefined\" || typeof document.addEventListener !== \"function\") return\n modalsRegistered = true\n // A morph of a root (a reply or broadcast morph, a page refresh) may have\n // inserted an open marked dialog or opened one; a page morph covers dialogs\n // outside any root.\n document.addEventListener(\"turbo:morph-element\", (event) => {\n if (event.target?.matches?.('[data-controller~=\"reactive\"]')) upgradeModals(event.target)\n })\n document.addEventListener(\"turbo:morph\", () => upgradeModals(document))\n // A morph that removes `open` from a modal dialog (the server now renders it\n // closed): close() it instead, which leaves the top layer, ends the page's\n // inertness and restores focus. Removing the attribute alone left all that\n // in place under the older spec text, and close() is a no-op once `open` is\n // gone — so the removal is cancelled and close() removes it. Idiomorph asks\n // \"remove\" for EVERY attribute of the old element, kept or not, and that ask\n // carries no new element: whether `open` is really leaving is read from the\n // element morph's new render, which comes first.\n const closing = new WeakSet()\n document.addEventListener(\"turbo:before-morph-element\", (event) => {\n const el = event.target\n const next = event.detail?.newElement\n if (!isDialog(el) || !next) return\n if (next.hasAttribute?.(\"open\")) closing.delete(el)\n else closing.add(el)\n })\n document.addEventListener(\"turbo:before-morph-attribute\", (event) => {\n const el = event.target\n const detail = event.detail\n if (detail?.attributeName !== \"open\" || detail.mutationType !== \"remove\") return\n if (!closing.has(el) || !el.open || !isModal(el)) return\n closing.delete(el)\n event.preventDefault()\n el.close()\n })\n}\n\n\n// --- keep: a morph re-applies what a kept client op did (issue #369) ---------\n// A morph rewrites `class`, `hidden` and every attribute from the server\n// render, so a selection made with on_client ops was lost to the next reply or\n// broadcast morph. An op with keep: records its RESULT on each target — each\n// class on or off, `hidden`, an attribute's value (null: absent) — and the one\n// document listener below puts that back after Turbo morphs the element in\n// place. The result, not the op: a toggle must not flip again on every morph.\n// The latest kept op on an element and a class or attribute wins, so \"clear\n// all, then select one\" leaves one. Classes and attributes no kept op touched\n// follow the render. Weak: an element a stream replaces is a new element.\n//\n// One Map per element: \".name\" → whether that class is on; an attribute name →\n// its value, null when absent. show/hide/toggle keep the `hidden` attribute,\n// so the morph puts back the visibility without running a transition again.\n// (No attribute name starts with \".\", so the two kinds of key never meet.)\n// An entry is re-inserted on every kept op, so the Map replays in op order:\n// a kept add_class after a kept set_attr(\"class\", …) still wins.\nconst kept = new WeakMap()\n\nfunction keepResult(el, name, args, resolveTargets) {\n const state = kept.get(el) ?? new Map()\n kept.set(el, state)\n const put = (key, value) => {\n state.delete(key)\n state.set(key, value)\n }\n if (name.endsWith(\"_class\")) for (const c of args.classes ?? []) put(`.${c}`, el.classList.contains(c))\n else if (name.endsWith(\"_attr\")) attrRefused(args.name) || put(args.name, el.getAttribute(args.name))\n else if (name === \"show\" || name === \"hide\" || name === \"toggle\") {\n put(\"hidden\", el.getAttribute(\"hidden\"))\n // The expanded: trigger's aria-expanded is kept on the trigger: a document\n // listener has no op scope to resolve it from later.\n if (args.expanded != null) {\n for (const trigger of resolveTargets({ ...args, to: args.expanded })) {\n keepResult(trigger, \"set_attr\", { name: \"aria-expanded\" })\n }\n }\n }\n}\n\nlet keepRegistered = false\n\nexport function registerReactiveKeep() {\n if (keepRegistered) return\n if (typeof document === \"undefined\" || typeof document.addEventListener !== \"function\") return\n keepRegistered = true\n // turbo:morph-element fires for every element Idiomorph morphed in place,\n // after its attributes were synced from the new render.\n document.addEventListener(\"turbo:morph-element\", (event) => {\n const el = event.target\n for (const [key, value] of kept.get(el) ?? []) {\n if (key[0] === \".\") el.classList.toggle(key.slice(1), value)\n else if (value == null) el.removeAttribute(key)\n else if (el.getAttribute(key) !== value) el.setAttribute(key, value)\n }\n })\n}\n\n\n// --- An image fallback that recovers (issue #383) ---------------------------\n// data-reactive-image-fallback='{\"hide\":true,\"alt\":\"#name\"}' on an <img>. On\n// `error` it gets data-reactive-image-state=\"error\" (plus `hidden` with hide,\n// and its alt shown in the `alt` node of its own reactive root); on `load`,\n// \"loaded\". A later src clears the error: copy_media and preview_file reset\n// it as they write (resetImage), any src that loads clears it on `load`, and\n// a morph that changes the src lets the browser try again. A morph that keeps\n// the src puts the state back. An error with no src is not an error (a\n// removed or emptied src). One document listener, capture phase (neither\n// event bubbles); nothing per root at connect.\nconst IMAGE_FALLBACK = \"data-reactive-image-fallback\"\nconst IMAGE_STATE = \"data-reactive-image-state\"\n// Marks the `hidden` the fallback set, so a reset removes only that one. On\n// the element, not in a WeakSet: a Turbo snapshot restores a NEW node.\nconst IMAGE_HIDDEN = \"data-reactive-image-hidden\"\nlet imageFallbackListeners = null\n\n// --- A wrapper an op hides (issues #391, #392) -------------------------------\n// preview_file's wrap: and reactive_image_fallback's wrap: hide the box around\n// an img. The mark says the runtime set that `hidden`, so a show removes only\n// a hide of its own, never one the author wrote. On the element, for the\n// IMAGE_HIDDEN reason. Visibility only: a wrapper usually holds the img, so\n// nothing here writes its text.\nconst WRAP_HIDDEN = \"data-reactive-wrap-hidden\"\n\nfunction hideWrap(el) {\n if (el.hasAttribute(\"hidden\")) return\n el.setAttribute(\"hidden\", \"\")\n el.setAttribute(WRAP_HIDDEN, \"\")\n}\n\nfunction showWrap(el) {\n if (!el.hasAttribute(WRAP_HIDDEN)) return\n el.removeAttribute(\"hidden\")\n el.removeAttribute(WRAP_HIDDEN)\n}\n\nfunction imageFallback(img) {\n if (img?.localName !== \"img\") return null\n const raw = img.getAttribute(IMAGE_FALLBACK)\n if (raw == null) return null\n try {\n return JSON.parse(raw) || {}\n } catch {\n return {}\n }\n}\n\nconst hasImageSrc = (img) => img.getAttribute(\"src\") || img.getAttribute(\"srcset\")\n\n// The node that shows the alt text: a selector resolved inside the img's own\n// root only (a document listener has no op scope).\nfunction imageRootNode(img, selector) {\n if (typeof selector !== \"string\" || !selector) return null\n try {\n return img.closest('[data-controller~=\"reactive\"]')?.querySelector(selector) ?? null\n } catch {\n return null\n }\n}\n\nfunction imageAltNode(img, wire) {\n const node = imageRootNode(img, wire.alt)\n // Never a node that holds the img: its textContent would wipe the img.\n return node && !node.contains(img) ? node : null\n}\n\nfunction setImageState(img, wire, value) {\n if (value === \"error\") {\n if (wire.hide && !img.hasAttribute(\"hidden\")) {\n img.setAttribute(\"hidden\", \"\")\n img.setAttribute(IMAGE_HIDDEN, \"\")\n }\n // wrap: (issue #392) may hold the img: only its visibility changes.\n if (wire.wrap) {\n const wrap = imageRootNode(img, wire.wrap)\n if (wrap) hideWrap(wrap)\n else console.warn(`[phlex-reactive] reactive_image_fallback wrap: ${JSON.stringify(wire.wrap)} is not in the root — skipped`)\n }\n const node = imageAltNode(img, wire)\n if (node) {\n node.textContent = img.getAttribute(\"alt\") ?? \"\"\n node.hidden = false\n }\n } else resetImage(img, wire)\n img.setAttribute(IMAGE_STATE, value)\n}\n\n// Undo what an error did — only what it did: `hidden` stays when the author\n// set it. Called by copy_media and preview_file when they change the src.\nfunction resetImage(img, wire = imageFallback(img)) {\n if (!wire || img.getAttribute(IMAGE_STATE) == null) return\n if (img.getAttribute(IMAGE_STATE) === \"error\") {\n if (img.hasAttribute(IMAGE_HIDDEN)) img.removeAttribute(\"hidden\")\n const wrap = imageRootNode(img, wire.wrap)\n if (wrap) showWrap(wrap)\n const node = imageAltNode(img, wire)\n if (node) node.hidden = true\n }\n img.removeAttribute(IMAGE_STATE)\n img.removeAttribute(IMAGE_HIDDEN)\n}\n\nfunction onImageEvent(event) {\n const img = event.target\n const wire = imageFallback(img)\n if (!wire) return\n if (event.type === \"load\") setImageState(img, wire, \"loaded\")\n else if (hasImageSrc(img)) setImageState(img, wire, \"error\")\n else resetImage(img, wire)\n}\n\nexport function registerReactiveImageFallback() {\n if (imageFallbackListeners) return\n if (typeof document === \"undefined\" || typeof document.addEventListener !== \"function\") return\n // A morph that keeps the src keeps the state: the render carries none, and\n // the browser loads nothing new to tell us again. Put it back once the\n // whole morph is done, so a later-morphed alt node is shown again too.\n const kept = new WeakMap()\n imageFallbackListeners = {\n error: onImageEvent,\n load: onImageEvent,\n \"turbo:before-morph-element\": (event) => {\n const img = event.target\n const next = event.detail?.newElement\n const value = img?.getAttribute?.(IMAGE_STATE)\n if (value == null || !next || !imageFallback(img)) return\n const same = (name) => img.getAttribute(name) === next.getAttribute?.(name)\n if (same(\"src\") && same(\"srcset\")) kept.set(img, value)\n },\n \"turbo:morph-element\": (event) => {\n const img = event.target\n const value = kept.get(img)\n if (value === undefined) return\n kept.delete(img)\n queueMicrotask(() => {\n const wire = imageFallback(img)\n if (wire) setImageState(img, wire, value)\n })\n },\n }\n for (const [name, fn] of Object.entries(imageFallbackListeners)) document.addEventListener(name, fn, true)\n // An image that finished before this ran (a cached 404) fired its event\n // unheard: read it off the element once. naturalWidth 0 is also a loaded\n // SVG with only a viewBox, so decode() decides (it rejects a broken image).\n for (const img of document.querySelectorAll?.(`img[${IMAGE_FALLBACK}]`) ?? []) {\n if (!img.complete || !hasImageSrc(img)) continue\n // A decode() that settles after the src changed decoded the OLD one: ignore it.\n const was = `${img.getAttribute(\"src\")} ${img.getAttribute(\"srcset\")}`\n const mark = (value) => `${img.getAttribute(\"src\")} ${img.getAttribute(\"srcset\")}` === was && setImageState(img, imageFallback(img) ?? {}, value)\n if (img.naturalWidth > 0) mark(\"loaded\")\n else if (typeof img.decode === \"function\") img.decode().then(() => mark(\"loaded\"), () => mark(\"error\"))\n else mark(\"error\")\n }\n}\n\n\n// For the opt-in entry: what to do with a stream before its render — hold it\n// for a module the incoming content needs. `handle(event, effects)` gets the\n// loaded effects module (or undefined) and wraps the stream with it itself.\nexport function onReactiveStreamWithoutEffects(handle) {\n streamWithoutEffects = handle\n}\n\n\n// --- A morph keeps the focused field's value (issue #338) ---\n// Turbo's stream morph (reply.morph, a broadcast morph) runs Idiomorph without\n// ignoreActiveValue, so a field being typed in took the render's value: a\n// normalised \"Hello\" over \"Hello \", or the value as the request left. Two\n// document listeners give a focused input/textarea inside a reactive root\n// Idiomorph's own ignoreActiveValue behaviour, minus the stale default:\n// before-morph-element — write the field's DEFAULT (its value attribute)\n// from the new render ourselves; with the user's\n// edit on it (the dirty-value flag) that never\n// touches .value, so dirty tracking still re-scans\n// against the saved value. A textarea's default is\n// its text child, which Idiomorph still morphs.\n// before-morph-attribute — cancel `value` on that field: Idiomorph honours it\n// for the attribute AND the property. Nothing else\n// (a root's token, a field's class) is held.\n// data-reactive-morph-value on a field lets the morph write it while focused.\n// --- An engine without contenteditable=\"plaintext-only\" (issue #355) ---\n// reactive_editable renders contenteditable=\"plaintext-only\": the browser\n// itself then pastes plain text and inserts \"\\n\" for Enter. An engine without\n// it reads the value as INVALID — the element is not editable, cannot even be\n// focused — so there every reactive editable is switched to \"true\" (and the\n// bindings feature does the plain-text paste itself). However it reaches the\n// page: present at load, appended by a stream into a root that is already\n// connected, loaded in a frame, written back by a morph of an unfocused\n// editable, or made reactive by a morph. One observer, only in such an engine; elsewhere the probe is all\n// that runs. Detected once per page.\nlet plaintextOnly\nlet plaintextObserver = null\nconst PLAINTEXT_EDITABLE = \"[data-reactive-editable][contenteditable=plaintext-only]\"\n\nfunction plaintextSupported() {\n if (plaintextOnly === undefined) {\n try {\n const probe = document.createElement(\"div\")\n probe.contentEditable = \"plaintext-only\"\n plaintextOnly = probe.contentEditable === \"plaintext-only\"\n } catch {\n plaintextOnly = false\n }\n }\n return plaintextOnly\n}\n\nfunction makeEditable(el) {\n if (el.matches?.(PLAINTEXT_EDITABLE)) el.setAttribute(\"contenteditable\", \"true\")\n el.querySelectorAll?.(PLAINTEXT_EDITABLE).forEach(makeEditable)\n}\n\nexport function registerReactivePlaintext() {\n // (No element factory, no page to observe: never probe a stub document.)\n if (plaintextObserver || typeof document === \"undefined\" || typeof document.createElement !== \"function\") return\n if (plaintextSupported()) return\n makeEditable(document)\n plaintextObserver = new MutationObserver((records) => {\n for (const record of records) {\n if (record.type === \"attributes\") makeEditable(record.target)\n else record.addedNodes.forEach(makeEditable)\n }\n })\n plaintextObserver.observe(document.documentElement, {\n childList: true,\n subtree: true,\n attributes: true,\n // (a morph can also make an existing plaintext-only element reactive)\n attributeFilter: [\"contenteditable\", \"data-reactive-editable\"],\n })\n}\n\n// For tests: what the probe answers (undefined probes again), with no\n// observer installed.\n\nlet morphFocusRegistered = false\n\n//\n// A reactive_editable (issue #351) is held the same way, but its \"value\" is its\n// CHILDREN: a focused one keeps them (text and caret) while every attribute\n// still morphs — copied here from the render, then the element's morph is\n// cancelled (cancelling is all-or-nothing in Idiomorph). Its contenteditable\n// stays as it is: the runtime switches it to \"true\" in an engine without\n// plaintext-only (registerReactivePlaintext, #355). Every morph of an editable, held or not, records the\n// render's text as its SERVER TEXT (the Escape revert, the dirty baseline).\nfunction holdsTypedValue(el) {\n return (\n el === document.activeElement &&\n (el.localName === \"input\" || el.localName === \"textarea\" || el.hasAttribute(\"data-reactive-editable\")) &&\n !el.hasAttribute(\"data-reactive-morph-value\") &&\n !!el.closest('[data-controller~=\"reactive\"]')\n )\n}\n\nexport function registerReactiveMorphFocus() {\n if (morphFocusRegistered) return\n if (typeof document === \"undefined\" || typeof document.addEventListener !== \"function\") return\n morphFocusRegistered = true\n document.addEventListener(\"turbo:before-morph-element\", (event) => {\n const el = event.target\n const next = event.detail?.newElement // absent when Turbo asks before a removal\n if (!next || event.defaultPrevented) return\n const holds = holdsTypedValue(el)\n // Before the default below is overwritten (issue #370).\n if (holds) noteHeld(el, next)\n if (el.hasAttribute?.(\"data-reactive-editable\")) {\n serverTexts.set(el, editableValue(next))\n if (!holds) return\n for (const { name } of [...el.attributes]) {\n if (!next.hasAttribute(name) && name !== \"contenteditable\" && name !== HELD) el.removeAttribute(name)\n }\n for (const { name, value } of next.attributes) {\n if (name !== \"contenteditable\" && el.getAttribute(name) !== value) el.setAttribute(name, value)\n }\n return event.preventDefault()\n }\n if (el.localName !== \"input\" || !holds) return\n const value = next.getAttribute(\"value\")\n if (value == null) el.removeAttribute(\"value\")\n else if (el.getAttribute(\"value\") !== value) el.setAttribute(\"value\", value)\n })\n // An editable a morph never reached (a stream append) records its server\n // text when first focused — before anything can be typed into it.\n document.addEventListener(\"focusin\", (event) => seedServerText(event.target))\n document.addEventListener(\"turbo:before-morph-attribute\", (event) => {\n const name = event.detail?.attributeName\n if ((name === \"value\" || name === HELD) && holdsTypedValue(event.target)) event.preventDefault()\n })\n // The user took theirs: the field reads its default again.\n document.addEventListener(\"input\", (event) => {\n const el = event.target\n if (el?.hasAttribute?.(HELD) && typedValue(el) === serverValue(el)) el.removeAttribute(HELD)\n })\n}\n\n// --- Knowing what was held (issue #370) ---\n// A morph that holds a focused field (above) over a render whose value differs\n// from the field's default — its last server value — means someone else changed\n// the field while the user typed. The field is marked data-reactive-held (kept\n// through later morphs while focused, like `value`), a bubbling reactive:held\n// { name, theirs } is dispatched, and the root's next action sends the names as\n// X-Reactive-Held. The root's own reply never marks (it answers exactly those\n// values), and once applied the root holds nothing — unless it refused them\n// (X-Reactive-Refused, issue #413): then the marks it names stay. A morph of\n// the field while unfocused writes the server's value and drops the mark with it.\nconst HELD = \"data-reactive-held\"\nconst ROOT = '[data-controller~=\"reactive\"]'\nlet replyRoot = null\nconst replying = new Map()\n\nfunction typedValue(el) {\n return el.hasAttribute(\"data-reactive-editable\") ? editableValue(el) : el.value\n}\n\nfunction serverValue(el) {\n return el.hasAttribute(\"data-reactive-editable\") ? serverText(el) : el.defaultValue\n}\n\nfunction renderedValue(next) {\n if (next.hasAttribute(\"data-reactive-editable\")) return editableValue(next)\n return next.localName === \"textarea\" ? next.textContent : (next.getAttribute(\"value\") ?? \"\")\n}\n\nfunction noteHeld(el, next) {\n const name = el.getAttribute(\"name\")\n // A checkbox's or radio's value is the option, not what the user typed.\n if (!name || /^(checkbox|radio)$/.test(el.type) || replying.has(el.closest(ROOT))) return\n const theirs = renderedValue(next)\n if (theirs === typedValue(el)) return el.removeAttribute(HELD)\n if (theirs === serverValue(el)) return\n el.setAttribute(HELD, \"\")\n el.dispatchEvent(new CustomEvent(\"reactive:held\", { bubbles: true, detail: { name, theirs } }))\n}\n\n// The names a root's next request reports: its own marked fields (not a nested\n// root's), still different from their default. As a header value: JSON with\n// every non-ASCII character escaped (fetch refuses a code point above 0xFF).\nfunction heldHeader(root) {\n const names = []\n for (const el of root.querySelectorAll(`[${HELD}]`)) {\n if (el.closest(ROOT) !== root) continue\n const name = el.getAttribute(\"name\")\n if (typedValue(el) === serverValue(el)) el.removeAttribute(HELD)\n else if (name && !names.includes(name)) names.push(name)\n }\n if (names.length === 0) return null\n return JSON.stringify(names).replace(/[\\u007f-]/g, (c) => `\\\\u${c.charCodeAt(0).toString(16).padStart(4, \"0\")}`)\n}\n\n// `keep`: true keeps every mark, a list keeps the marks of those names.\nfunction releaseHeld(root, keep) {\n if (keep === true) return\n for (const el of root.querySelectorAll(`[${HELD}]`)) {\n if (el.closest(ROOT) === root && !keep?.includes(el.getAttribute(\"name\"))) el.removeAttribute(HELD)\n }\n}\n\n// What a reply refused (issue #413): \"*\" keeps every mark, a JSON list the\n// marks of those names. Anything else reads as a plain reply.\nfunction refusedHeader(response) {\n const raw = response.headers.get(\"X-Reactive-Refused\")\n if (raw === \"*\") return true\n try {\n const names = JSON.parse(raw)\n return Array.isArray(names) ? names : null\n } catch {\n return null\n }\n}\n\n// Hand a root's own reply to Turbo: while its streams render nothing in the\n// root is marked, and once they have the root holds nothing but what `keep`\n// names. Turbo announces each stream synchronously inside renderStreamMessage\n// (streamOwnReply wraps its render, innermost); a reply none of whose streams\n// rendered releases now. An error reply (keep true) never marks either, but\n// releases nothing: the server refused those values rather than answering them.\nfunction renderOwnReply(root, html, keep = null) {\n const own = { root, streams: 0, keep }\n const previous = replyRoot\n replyRoot = own\n try {\n window.Turbo.renderStreamMessage(html)\n } finally {\n replyRoot = previous\n }\n if (own.streams === 0) releaseHeld(root, keep)\n}\n\nfunction streamOwnReply(event) {\n const own = replyRoot\n const detail = event.detail\n const render = detail?.render\n if (!own || typeof render !== \"function\") return\n own.streams++\n detail.render = async (el) => {\n replying.set(own.root, (replying.get(own.root) || 0) + 1)\n try {\n return await render(el)\n } finally {\n const left = replying.get(own.root) - 1\n if (left) replying.set(own.root, left)\n else replying.delete(own.root)\n releaseHeld(own.root, own.keep)\n }\n }\n}\n\n\n// The framework-owned act a reactive_lazy(on:) shell sends (issue #276).\n// Lockstep with Phlex::Reactive::Component::Lazy::MATERIALIZE_ACTION.\nconst LAZY_MATERIALIZE_ACTION = \"__materialize\"\n\n// Offline CSS hook (issue #101). Mirror data-reactive-offline on\n// document.documentElement from navigator.onLine, kept in sync by the window\n// online/offline events — so an app can dim a save button or show a banner with\n// PURE CSS and zero JS ([data-reactive-offline] .save { pointer-events: none }).\n// Guarded on window (needed for addEventListener AND navigator) so importing the\n// module in a non-browser (bun test) context is a no-op, and registered once\n// (the online/offline listeners are NOT {once}, so a second registerReactiveActions\n// call must not stack duplicates) — mirroring the dismiss guard + reset seam.\nlet offlineRegistered = false\nexport function registerReactiveOffline() {\n if (offlineRegistered) return\n if (typeof window === \"undefined\" || typeof document === \"undefined\") return\n if (typeof window.addEventListener !== \"function\") return\n offlineRegistered = true\n // toggleAttribute(name, force) writes data-reactive-offline=\"\" (a bare boolean\n // attr the [data-reactive-offline] selector matches) or removes it — never the\n // \"true\" string. navigator.onLine === false is the reliable direction (a false\n // \"online\" is spec-permitted but rare, and this is only a presentational hook —\n // the authoritative offline signal is the #perform gate, not this attribute).\n // Fully defensive: a missing documentElement/toggleAttribute/navigator degrades\n // to a no-op — a presentational hook must NEVER throw during bootstrap.\n const sync = () => {\n const root = document.documentElement\n if (typeof root?.toggleAttribute !== \"function\") return\n root.toggleAttribute(\"data-reactive-offline\", globalThis.navigator?.onLine === false)\n }\n sync() // seed synchronously so first paint is correct\n window.addEventListener(\"online\", sync)\n window.addEventListener(\"offline\", sync)\n}\n\n\n// Latency simulator dev aid (issue #102): features/devtools.js (issue #275). The\n// core keeps the two places that decide whether the module is needed at all:\n// the page opted into the console handle (the phlex-reactive-env meta), or a\n// delay is stored for this tab. Read live per request, like the CSRF token.\nconst LATENCY_KEY = \"phlex-reactive:latency\"\n\nfunction latencyStored() {\n return typeof sessionStorage !== \"undefined\" && Boolean(sessionStorage.getItem(LATENCY_KEY))\n}\n\n// At registration: a page that can use the simulator gets the module now —\n// effectively eager, in development only. No meta and no stored delay: no\n// module, no global handle, nothing on the page.\n//\n// This runtime registers with Turbo while it is being evaluated, BEFORE the\n// entry that imports it has run — before the default entry has handed the\n// module over, before the opt-in entry has said where to import it from. So\n// with neither in place this does nothing, and each entry calls it again\n// once it has done its part (see reactive_controller.js and core.js).\nexport function registerReactiveDev() {\n if (typeof document === \"undefined\") return\n if (__SPLIT__ && !featureModules.has(\"devtools\") && !FEATURES.get(\"devtools\")[1]) return\n const development = document.querySelector?.('meta[name=\"phlex-reactive-env\"]')?.content === \"development\"\n // (The default entry has the module in it: there is nothing to load, and\n // attach() is idempotent, so the second call is harmless.)\n if (!__SPLIT__) {\n if (development) devtoolsFeature.attach()\n } else if (development || latencyStored()) withFeature(\"devtools\", (devtools) => development && devtools.attach())\n}\n\n// --- Global reactive-activity signal (issue #201) --------------------------\n// A DOCUMENT-LEVEL count of in-flight reactive operations — the direct analogue\n// of Turbo's progress bar, but for reactive round trips and deferred renders\n// instead of navigations. Anything that starts an async reactive operation calls\n// enterReactiveActivity(); when it settles (success OR failure, on every path) it\n// calls exitReactiveActivity(). The count is exposed two ways so an app — or a\n// system test — can key off \"is the reactive layer settling?\" without knowing\n// about any individual root:\n//\n// * a marker on <html>: data-reactive-active present while count > 0 (CSS can\n// drive a global spinner; code/tests can read it). A DISTINCT name from the\n// per-root data-reactive-busy so a [data-reactive-busy] selector never also\n// matches the document element.\n// * events on document: reactive:busy on the 0 -> >0 edge, reactive:idle on the\n// >0 -> 0 edge — EDGES ONLY (not once per op), each carrying { count }.\n//\n// It sums ACROSS all reactive roots (module-level, not per-controller) and across\n// the two async lifecycles wired below:\n// * dispatch — entered in #applyBusy (at ENQUEUE, so the queue wait counts too),\n// exited in the settle closure #perform runs in its finally.\n// * defer — entered/exited with the pendingDefers registry (set/delete), the\n// ONE registry both the fetch (pull) and the stream (push) lane maintain — so\n// the push lane stays balanced even though it clears its pending markers by a\n// node swap, not clearDeferPending. A supersede is delete-then-set (net zero),\n// which is correct: a fast typist's replaced defer is still \"layer busy\".\n//\n// compute-seed is deliberately NOT counted: recompute() is synchronous, so a seed\n// is fully applied by the time the call returns — there is no async window to await\n// (the \"value settles a beat after a morph/seed\" case the issue describes is\n// covered by the System test helpers' re-resolve-by-id polling, not this counter).\nexport const ACTIVE_ATTR = \"data-reactive-active\"\n\nlet activityCount = 0\n\n// Increment the global in-flight count; on the 0 -> 1 edge, mark <html> and fire\n// reactive:busy. Fully defensive — a non-browser/test document with no\n// documentElement/dispatchEvent still tracks the count and simply skips the DOM\n// side effects (a global signal must never throw during bootstrap or a round trip).\nexport function enterReactiveActivity() {\n activityCount++\n if (activityCount === 1) syncReactiveActivity(\"reactive:busy\")\n}\n\n// Decrement the global in-flight count, clamped at 0 so an unbalanced exit can\n// never drive it negative (which would wedge the marker on forever). On the\n// 1 -> 0 edge, clear the <html> marker and fire reactive:idle.\nexport function exitReactiveActivity() {\n if (activityCount === 0) return\n activityCount--\n if (activityCount === 0) syncReactiveActivity(\"reactive:idle\")\n}\n\n// The current in-flight count — a test seam and a runtime read (an app can gate an\n// \"unsaved changes\" prompt on `reactiveActivityCount() > 0`).\nexport function reactiveActivityCount() {\n return activityCount\n}\n\n// Test seam: reset the module-level counter (and clear the marker) between tests,\n// since the module is imported once per bun run.\nexport function resetReactiveActivity() {\n activityCount = 0\n const root = typeof document !== \"undefined\" ? document.documentElement : null\n root?.removeAttribute?.(ACTIVE_ATTR)\n}\n\n// Write the <html> marker from the current count and fire the edge event on\n// document. Both sides are independently guarded so a partial document stub (a\n// documentElement without toggleAttribute, or a document without dispatchEvent)\n// degrades to a no-op rather than throwing.\nfunction syncReactiveActivity(eventName) {\n if (typeof document === \"undefined\") return\n const root = document.documentElement\n if (typeof root?.toggleAttribute === \"function\") {\n root.toggleAttribute(ACTIVE_ATTR, activityCount > 0)\n }\n if (typeof document.dispatchEvent === \"function\" && typeof CustomEvent === \"function\") {\n document.dispatchEvent(new CustomEvent(eventName, { detail: { count: activityCount } }))\n }\n}\n\n// --- Reactive request totals (issue #279) ----------------------------------\n// A running total of the reactive REQUESTS made, per kind — \"action\" (one per\n// #perform fetch) and \"defer\" (one per performDeferFetch) — so a system test can\n// assert \"one request on first open, none after\". The in-flight count above says\n// whether the layer is settling; this says how much it did.\n//\n// The JSON on <html data-reactive-requests> IS the store (read-modify-write per\n// request): a test reads it, and re-baselines by writing zeros, with no window\n// hook and no module state to drift from it. Written ONLY under the verbose gate\n// — data-reactive-verbose on <html> or on any reactive root (stamped in dev/test\n// by default) — so production writes nothing. The gate is checked once per\n// network request, never per event.\nexport const REQUESTS_ATTR = \"data-reactive-requests\"\n\nexport function countReactiveRequest(kind) {\n if (typeof document === \"undefined\") return\n const root = document.documentElement\n if (typeof root?.setAttribute !== \"function\" || !reactiveRequestsCounted(root)) return\n const totals = readReactiveRequests(root)\n totals[kind] = (Number(totals[kind]) || 0) + 1\n root.setAttribute(REQUESTS_ATTR, JSON.stringify(totals))\n}\n\nfunction reactiveRequestsCounted(root) {\n return root.hasAttribute?.(\"data-reactive-verbose\") || !!document.querySelector?.('[data-reactive-verbose=\"true\"]')\n}\n\n// The current totals, or zeros when the attribute is absent or not an object\n// (a hand-edited / garbled value restarts the count rather than throwing).\nfunction readReactiveRequests(root) {\n try {\n const parsed = JSON.parse(root.getAttribute(REQUESTS_ATTR))\n if (parsed && typeof parsed === \"object\") return { action: 0, defer: 0, ...parsed }\n } catch {}\n return { action: 0, defer: 0 }\n}\n\nexport function registerReactiveActions() {\n registerReactiveVisit()\n registerReactiveToken()\n registerReactiveJs()\n registerReactiveDefer()\n registerReactiveStreamRender()\n registerReactiveMorphFocus()\n registerReactiveModals()\n registerReactiveKeep()\n registerReactiveImageFallback()\n registerReactivePlaintext()\n registerReactiveOffline()\n registerReactiveDev()\n}\n\n// Escape a DOM id for safe interpolation into a RegExp (an id can legally contain\n// regex metacharacters like `.`/`:` — e.g. an `escape:`-namespaced or dotted id).\n// Used by #extractToken to match the stream that re-renders THIS element by id.\nexport function escapeRegExp(string) {\n return string.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")\n}\n\n// --- Loaded-twice guard (issue #275) ------------------------------------------\n// Each shipped entry carries its own copy of this file. An app that imports\n// BOTH phlex/reactive/reactive_controller and phlex/reactive/core (or bundles\n// one and pins the other) gets two runtimes: two request queues' worth of\n// module state, two activity counters, two feature tables. Say so, loudly.\nconst CORE_LOADED = Symbol.for(\"phlex-reactive.core\")\nif (globalThis[CORE_LOADED]) {\n console.error(\n \"[phlex-reactive] the client was loaded twice. Import EITHER phlex/reactive/reactive_controller (the default, \" +\n \"everything in one file) OR phlex/reactive/core (features on demand), not both — each carries its own state.\",\n )\n}\nglobalThis[CORE_LOADED] = true\n\n// --- Registration guard (issue #26 part 2) -------------------------------\n// In a `lazyLoadControllersFrom(\"controllers\", application)` app, only\n// controllers under app/javascript/controllers/ are registered. This module\n// lives outside that dir, so importing it isn't enough — `data-controller=\n// \"reactive\"` does NOTHING until the host runs application.register(\"reactive\",\n// ...). The failure is silent: components render, but no action ever fires.\n//\n// We can't warn from connect() in that case (connect never runs). Instead, once\n// the page is ready, if reactive elements exist but no controller has connected,\n// the controller wasn't registered — so we warn, pointing at the fix.\nlet reactiveConnected = false\n\nexport function checkReactiveRegistration() {\n if (reactiveConnected) return\n if (typeof document === \"undefined\") return\n const els = document.querySelectorAll('[data-controller~=\"reactive\"]')\n if (!els || els.length === 0) return\n console.warn(\n \"[phlex-reactive] found \" + els.length + ' element(s) with data-controller=\"reactive\" ' +\n \"but the reactive controller never connected. It is loaded but not registered — \" +\n 'add `application.register(\"reactive\", ReactiveController)` (importmap) or import it ' +\n \"into app/javascript/controllers/ for lazyLoadControllersFrom apps. See the README.\"\n )\n}\n\n// Test seams (no-ops in production usage).\n\nif (typeof window !== \"undefined\" && typeof document !== \"undefined\") {\n // Defer past initial controller connection (a microtask/tick after ready).\n const scheduleCheck = () => setTimeout(checkReactiveRegistration, 0)\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", scheduleCheck, { once: true })\n } else {\n scheduleCheck()\n }\n}\n\n// The interpret-time attribute-name allowlist (issue #96) — the SECOND half of\n// the two-sided default-deny. The Ruby builder already refuses these at build\n// time; this guards a hand-built / forged ops attr from bypassing it. Refused:\n// event handlers (on*, XSS), URL-bearing names (a javascript: navigation\n// surface), and style (CSS injection). Case-insensitive, mirroring js.rb.\n// srcset (issue #373) too: a URL list that picks what an <img> loads.\nconst REFUSED_ATTR_URL = new Set([\"href\", \"src\", \"srcset\", \"srcdoc\", \"action\", \"formaction\", \"xlink:href\", \"style\"])\nfunction attrRefused(name) {\n const lower = String(name).toLowerCase()\n return lower.startsWith(\"on\") || REFUSED_ATTR_URL.has(lower)\n}\n\n// Run an animated visibility change (issue #96 `transition:`). `flip` performs\n// the actual hidden-flag change; `[during, from, to]` are class lists applied\n// AROUND it. Cleanup is awaited via the element's OWN animationend/transitionend\n// OR a setTimeout fallback — whichever comes first — so an element with NO\n// animation never leaves the helper classes stuck (the op chain itself is not\n// blocked: cleanup is fire-and-forget, later ops run immediately). Both end\n// events bubble, so a descendant's is ignored, and settling drops both\n// listeners (the effects.js fix, #296). The fallback is armed synchronously,\n// not behind the frame, so a hidden tab (no rAF) still cleans up; cleanup then\n// cancels the pending frame and clears `from` too, so a late frame can't leave\n// `to` (or `from`) stuck. A one-shot `done` guard runs cleanup exactly once.\n// One run per element: a new run first settles the live one (its classes,\n// timer, listeners and frame), so a superseded run's late wakeups can't strip\n// the new run's classes. The fallback follows the element's computed\n// durations (transitionFallbackMs), so a 600 ms transition isn't cut at 350.\nconst transitionRuns = new WeakMap()\nfunction runTransition(el, transition, flip) {\n const [during, from, to] = transition\n transitionRuns.get(el)?.()\n el.classList.add(during, from)\n flip()\n let done = false\n const frame = requestAnimationFrame(() => {\n if (done) return\n el.classList.remove(from)\n el.classList.add(to)\n })\n\n const cleanup = (event) => {\n if (done || (event && event.target !== el)) return\n done = true\n transitionRuns.delete(el)\n clearTimeout(timer)\n globalThis.cancelAnimationFrame?.(frame)\n el.removeEventListener(\"animationend\", cleanup)\n el.removeEventListener(\"transitionend\", cleanup)\n el.classList.remove(during, from, to)\n }\n transitionRuns.set(el, cleanup)\n el.addEventListener(\"animationend\", cleanup)\n el.addEventListener(\"transitionend\", cleanup)\n // Also the ONLY path for a non-animated element (no end event fires there),\n // so it must always be scheduled — and never behind the frame.\n const timer = setTimeout(cleanup, transitionFallbackMs(el))\n}\n\n// The longest computed duration × iteration-count + delay over the transition\n// and animation lists (CSS repeats the shorter list; a transition runs once,\n// `infinite` hits the cap), plus 50 ms; 350 ms when nothing is declared (or\n// there is no computed style), capped at 5 s so a bogus value can't wedge the\n// classes on. Read once `during` is on the element.\nfunction transitionFallbackMs(el) {\n let longest = 0\n try {\n const style = getComputedStyle(el)\n for (const kind of [\"transition\", \"animation\"]) {\n const [durations, delays] = [\"Duration\", \"Delay\"].map((name) =>\n String(style[kind + name])\n .split(\",\")\n .map((v) => parseFloat(v) * (/ms/.test(v) ? 1 : 1000) || 0),\n )\n const counts = String(style[kind + \"IterationCount\"])\n .split(\",\")\n .map((v) => (/inf/.test(v) ? Infinity : parseFloat(v) >= 0 ? parseFloat(v) : 1))\n for (let i = 0; i < durations.length || i < delays.length || i < counts.length; i++) {\n const runs = durations[i % durations.length] * counts[i % counts.length] || 0\n longest = Math.max(longest, runs + delays[i % delays.length])\n }\n }\n } catch {}\n return Math.min(Math.max(longest + 50, 350), 5000)\n}\n\n// Rich-text and contenteditable fields, as #collectFields reads them (minus\n// the [name] guard — an editor's name may live on its IDL `name` getter:\n// Trix's `input=`-paired hidden input). The persist feature keeps its own copy\n// of these two constants (features never import the core); a test pins that\n// they stay equal.\nexport const EDITOR_SELECTOR =\n \":is(lexxy-editor, trix-editor, [contenteditable=''], [contenteditable=true], [contenteditable=plaintext-only])\"\nexport const EDITOR_TAGS = new Set([\"lexxy-editor\", \"trix-editor\"])\n\n// A reactive_editable's value (issue #351): its text with line breaks — a\n// <br> is one, a block element (the <div> a contenteditable=true Enter\n// inserts) starts and ends a line — \"\\r\\n\" normalised, ONE trailing newline\n// dropped (the <br> browsers keep at the end), and folded to spaces when the\n// field is single-line. Read from the DOM, not innerText: no layout, the same\n// on a detached render and under any white-space.\nexport function editableValue(el) {\n let text = \"\"\n // A block's edge asks for a line break; the next text or <br> pays it,\n // once, unless the text already ends in one.\n let line = false\n const add = (s) => {\n if (line && text && !text.endsWith(\"\\n\")) text += \"\\n\"\n line = false\n text += s\n }\n const walk = (node) => {\n for (const child of node.childNodes) {\n const block = /^(div|p|li)$/.test(child.localName)\n if (block) line = true\n if (child.nodeType === 3) child.data && add(child.data)\n else if (child.localName === \"br\") add(\"\\n\")\n else if (child.nodeType === 1) walk(child)\n if (block) line = true\n }\n }\n walk(el)\n text = text.replace(/\\r\\n?/g, \"\\n\").replace(/\\n$/, \"\")\n return el.getAttribute(\"data-reactive-editable\") === \"multi\" ? text : text.replace(/\\n/g, \" \")\n}\n\n// element -> the text of its last server render (issue #351): what Escape\n// reverts to and what dirty tracking diffs against. Seeded when the root\n// connects (or on first focus) and advanced by every morph of the element.\nconst serverTexts = new WeakMap()\n\nfunction seedServerText(el) {\n if (el?.hasAttribute?.(\"data-reactive-editable\") && !serverTexts.has(el)) serverTexts.set(el, editableValue(el))\n}\n\nexport function serverText(el) {\n seedServerText(el)\n return serverTexts.get(el)\n}\n\n// A RICH editor (lexxy/trix) is only ready once its custom element upgraded —\n// Trix defines its elements in a setTimeout after load — and exposes the\n// string `value` accessor; reading it before that yields \"\", which is issue\n// #8. A bare [contenteditable] is plain DOM and always ready.\nfunction collectorEditorReady(el) {\n return EDITOR_TAGS.has(el.localName) ? typeof el.value === \"string\" : true\n}\n\n// The client-op whitelist behind on_client (issue #95, extended in #96). Mirrors\n// Phlex::Reactive::JS's vocabulary; an op name not in this map is\n// warn-and-skipped by #applyOps (client-side default-deny — a stale or newer\n// ops attr must never break the page). Each op is a pure, local DOM mutation:\n// nothing is sent anywhere, and nothing is read back — with ONE deliberate\n// exception, paste_into (issue #228), which reads the clipboard behind the\n// browser's own gesture + permission gates and still only writes locally.\n// Frozen so nothing can be registered into it at runtime — extending the\n// vocabulary is a gem change, not an app hook.\nconst CLIENT_OPS = Object.freeze({\n show: (el, args, resolveTargets) => setVisibility(el, false, args, resolveTargets),\n hide: (el, args, resolveTargets) => setVisibility(el, true, args, resolveTargets),\n toggle: (el, args, resolveTargets) => setVisibility(el, !el.hidden, args, resolveTargets),\n add_class: (el, args) => el.classList.add(...(args.classes ?? [])),\n remove_class: (el, args) => el.classList.remove(...(args.classes ?? [])),\n toggle_class: (el, args) => (args.classes ?? []).forEach((c) => el.classList.toggle(c)),\n\n // Attribute ops (issue #96), interpret-time allowlisted. set_attr writes the\n // (already-stringified) value; toggle_attr adds a missing attr (value \"\") or\n // removes a present one — or, given values [on, off] (issue #271), flips\n // between them (an absent attr becomes `on`); remove_attr removes it. A\n // refused name warns + skips.\n set_attr: (el, args) => {\n if (guardAttr(args.name)) el.setAttribute(args.name, args.value ?? \"\")\n },\n remove_attr: (el, args) => {\n if (guardAttr(args.name)) el.removeAttribute(args.name)\n },\n toggle_attr: (el, args) => {\n if (!guardAttr(args.name)) return\n if (Array.isArray(args.values)) {\n const [on, off] = args.values\n el.setAttribute(args.name, el.getAttribute(args.name) === on ? off : on)\n } else if (el.hasAttribute(args.name)) el.removeAttribute(args.name)\n else el.setAttribute(args.name, \"\")\n },\n\n // Focus ops (issue #96). focus targets the match itself; focus_first targets\n // its first focusable descendant (opened-menu → first menuitem).\n focus: (el) => el.focus?.(),\n focus_first: (el) => firstFocusable(el)?.focus?.(),\n // Issue #399: blur the focused element when it is the target or inside it\n // (Escape ends a menu's :focus-within); focus elsewhere is left alone.\n // ACTOR-ONLY like focus (BROADCAST_REFUSED_OPS server-side).\n blur: (el) => {\n const active = el.ownerDocument?.activeElement ?? globalThis.document?.activeElement\n if (active && (active === el || el.contains?.(active))) active.blur?.()\n },\n\n // Modal dialog ops (issue #365): HTMLDialogElement#showModal / #close.\n // ACTOR-ONLY like focus (BROADCAST_REFUSED_OPS server-side).\n show_modal: (el) => showModal(el),\n close_dialog: (el) => closeDialog(el),\n\n // Text op (issue #159): set textContent — XSS-safe by construction (never\n // innerHTML), strictly less powerful than set_attr. Change-guarded like\n // #mirrorText. With global: true it is the cross-root text escape: paint a\n // value into a recap node OUTSIDE the component's root.\n text: (el, args) => {\n const text = String(args.value ?? \"\")\n if (el.textContent !== text) el.textContent = text\n },\n\n // Copy a server-rendered image (issue #373): src, srcset and alt from the\n // `from` <img>, resolved with the op's own scoping (the `submitter:`\n // precedent). The URL is read off that element, never from an argument —\n // the src refusal above stays. A selector that does not parse (a URL\n // string) is a miss, like one that matches nothing.\n copy_media: (el, args, resolveTargets) => {\n let source = null\n try {\n source = resolveTargets({ ...args, to: args.from })[0] ?? null\n } catch {}\n copyMedia(el, source)\n },\n\n // Tick or untick a whole checkbox group (issue #342) — \"clear selection\".\n // The bindings feature flips the root's OWNED boxes the way its select-all\n // header does (every box set, then input + change on each flipped one) and\n // re-syncs the group bindings once. global: true ignores the op's own root\n // and asks every reactive root on the page; each flips only the boxes it\n // owns (nested roots included), so every root holding the group answers.\n check_group: (el, args) => {\n const roots = args.global ? document.querySelectorAll('[data-controller~=\"reactive\"]') : [el]\n for (const root of roots) {\n __SPLIT__\n ? withFeature(\"bindings\", (bindings) => bindings.checkGroup(root, args.group, args.checked))\n : bindingsFeature.checkGroup(root, args.group, args.checked)\n }\n },\n\n // Write this tab's presence state (issue #358) through the root's\n // presence_set action. The feature warns outside a reactive_presence root.\n presence: (el, args) =>\n __SPLIT__\n ? withFeature(\"presence\", (presence) => presence.set(el, args.state))\n : presenceFeature.set(el, args.state),\n\n // Dispatch a bubbling CustomEvent (issue #96). RAW element.dispatchEvent — the\n // controller SHADOWS Stimulus's this.dispatch helper, so it must not be used.\n dispatch: (el, args) => {\n el.dispatchEvent(new CustomEvent(args.name, { bubbles: true, composed: true, detail: args.detail ?? {} }))\n },\n\n // Submit the target's OWN form (issue #226) via requestSubmit() — constraint\n // validation runs and a REAL cancelable `submit` event fires, so an\n // on(:action, event: \"submit\") interception or a native/Turbo form handles it\n // exactly like a user submit. No form → no-op. ACTOR-ONLY like focus: the\n // broadcast builder refuses it server-side (BROADCAST_REFUSED_OPS).\n // `submitter:` (issue #319) resolves with the op's own scoping (the\n // `expanded:` precedent) and submits THROUGH it, so its name=value posts. It\n // must be a submit control of that form — requestSubmit(x) throws otherwise —\n // so anything else warns and falls back to a plain requestSubmit().\n submit: (el, args, resolveTargets) => {\n const form = submitFormFor(el)\n if (!form) return\n let submitter = null\n if (args?.submitter != null && typeof resolveTargets === \"function\") {\n submitter = resolveTargets({ ...args, to: args.submitter })[0] ?? null\n if (!(submitter?.form === form && (submitter.type === \"submit\" || submitter.type === \"image\"))) {\n console.warn(`[phlex-reactive] submitter ${args.submitter} is not a submit control of the form — ignored`)\n submitter = null\n }\n }\n submitter ? form.requestSubmit?.(submitter) : form.requestSubmit?.()\n },\n\n // Clipboard-source paste (issue #228): on a user gesture, read\n // navigator.clipboard.readText() and feed the text into the target field\n // through the normal input pipeline — exactly what a native Cmd/Ctrl+V does.\n // The ONE op that reads a browser API and is async: fire-and-forget, so\n // applyOps stays sync and chain siblings never wait (the runTransition\n // posture). A rejected/dismissed read, empty text, or a missing API is a\n // SILENT no-op — page state must not change. ACTOR-ONLY like focus/submit:\n // the broadcast builder refuses it server-side (BROADCAST_REFUSED_OPS) —\n // and that server gate is the REAL one. The browser only partially backs it\n // up: Safari gates every read on a fresh gesture and Firefox shows its\n // paste picker per read, but Chromium's clipboard-read is a PERSISTENT\n // per-origin permission — once granted (the legit paste button itself\n // induces that), readText() succeeds with no gesture. No client-side\n // refusal is possible here: the reactive:js interpreter cannot distinguish\n // an actor reply's stream from a broadcast's, and reply.js legitimately\n // carries this op.\n paste_into: (el) => pasteClipboardInto(el),\n\n // Preview a chosen file (issue #375): paint the input's first image file\n // onto the `into` <img> through an object URL made HERE from the input's\n // own File. The op carries no URL and `args.url` is never read, so the src\n // refusal of set_attr (issue #96) stays whole. ACTOR-ONLY like paste_into\n // (BROADCAST_REFUSED_OPS server-side).\n preview_file: (el, args, resolveTargets) => previewFile(el, args, resolveTargets),\n\n // A field's live value (issue #374): the paste_into write without the\n // clipboard or the focus. ACTOR-ONLY (BROADCAST_REFUSED_OPS server-side).\n set_value: (el, args) => setFieldValue(el, String(args.value ?? \"\")),\n\n // Client-only drafts (issue #239): merge a flat state bag into the root's\n // reactive_persist draft / forget the draft. ACTOR-ONLY like focus/submit\n // (BROADCAST_REFUSED_OPS server-side) — rewriting or wiping every\n // subscriber's draft from a broadcast would be hostile.\n // The draft code lives in the persist feature module (issue #275), so both\n // ops run once it has loaded — at once on a root that restored a draft.\n persist_state: (el, args) =>\n __SPLIT__\n ? withFeature(\"persist\", (persist) => persist.writeState(el, args.state))\n : persistFeature.writeState(el, args.state),\n persist_clear: (el) =>\n __SPLIT__ ? withFeature(\"persist\", (persist) => persist.clearRoot(el)) : persistFeature.clearRoot(el),\n})\n\n// The form a submit op commits (issue #226), in order: the target itself when\n// it IS a form (tagName, not instanceof — fake-node/test friendly), its form\n// owner for a control (input.form — honors a form= attribute), else the nearest\n// ancestor form. closest() may cross the component root by design — the\n// FIELD'S OWN form is the submit scope, not the reactive boundary.\nfunction submitFormFor(el) {\n if (el?.tagName === \"FORM\") return el\n return el?.form ?? el?.closest?.(\"form\") ?? null\n}\n\n// Read the clipboard into a field (issue #228) — the body of the paste_into\n// op. The write mirrors a native paste: set .value, dispatch a bubbling\n// `input` event (the set-value + dispatch contract, issue #183 — compute\n// reducers, show bindings, and on_complete all run exactly as if the user\n// had typed), then focus (the caret lands where the user continues typing on\n// a partial paste). Availability-guarded: insecure contexts and some\n// webviews have no navigator.clipboard — the connect()-time gate hides\n// marked triggers there, so this guard is belt-and-braces. Empty text is a\n// no-op: \"paste nothing\" must not clear a half-typed field.\nfunction pasteClipboardInto(field) {\n const clipboard = globalThis.navigator?.clipboard\n if (typeof clipboard?.readText !== \"function\") return\n clipboard\n .readText()\n .then((text) => {\n if (!text) return\n field.value = text\n dispatchInput(field)\n field.focus?.()\n })\n .catch(() => {\n // Permission denied or the prompt dismissed — the browser's own UX said\n // no. The issue-#228 contract: a silent no-op, never an error.\n })\n}\n\n// --- Preview a chosen file (issue #375) ---\n// img -> the object URL preview_file last made for it. The runtime owns it:\n// never an op argument, never an attribute a caller sets. An entry lives\n// while the img is on the page AND still shows that URL; a re-pick, a clear,\n// a non-image file, the img leaving the page or a morph that strips its src\n// in place revokes it. One MutationObserver notices the last two, and only\n// while some preview is live: it then wakes on every DOM mutation of the page\n// (a live feed, a drag), and each wake is a walk over the few live previews.\nconst previews = new Map()\nlet previewObserver = null\n\nfunction previewFile(input, args, resolveTargets) {\n if (input?.tagName !== \"INPUT\" || input.type !== \"file\") {\n return warnPreview(`reads an input[type=file], not <${input?.localName}>`)\n }\n const img = typeof resolveTargets === \"function\" ? resolveTargets({ ...args, to: args.into })[0] : null\n // Duck-typed on tagName (the isDialog precedent): only an <img> ever gets a\n // src here — never an iframe, a script, a frame or anything else.\n if (img?.tagName !== \"IMG\") {\n return warnPreview(`paints an <img>, and ${JSON.stringify(args.into)} is ${img ? `a <${img.localName}>` : \"not on the page\"}`)\n }\n // valid: true (issue #382): an input the app marked invalid (setCustomValidity,\n // required) previews as empty. validity.valid, not checkValidity(): no\n // `invalid` event fired into the app's own listeners.\n const files = [...(args.valid === true && input.validity?.valid === false ? [] : (input.files ?? []))]\n const accepts = previewAccepts(input, args.accept)\n const listed = files.filter((f) => typeof f?.type === \"string\" && accepts(f))\n // Only an image is painted: a listed PDF clears the preview, unrefused.\n let file = listed.find((f) => f.type.startsWith(\"image/\"))\n // A refused file (issue #391) clears like an empty pick and says why. The\n // input keeps its files and its validity: the page owns the reset. Size is\n // read BEFORE an object URL exists, so an over-size file never gets one.\n let reason = files.length > 0 && listed.length === 0 ? \"type\" : null\n if (file && Number.isInteger(args.max_bytes) && file.size > args.max_bytes) reason = \"size\"\n const refused = reason === \"size\" ? file : files[0]\n if (reason) file = null\n forgetPreview(img)\n if (file) {\n const url = URL.createObjectURL(file)\n previews.set(img, url)\n img.setAttribute(\"src\", url)\n watchPreviews()\n } else img.removeAttribute(\"src\")\n resetImage(img)\n if (args.wrap != null) {\n const wraps = resolveTargets({ ...args, to: args.wrap })\n if (wraps.length === 0) warnPreview(`wrap: ${JSON.stringify(args.wrap)} is not on the page`)\n for (const wrap of wraps) file ? showWrap(wrap) : hideWrap(wrap)\n }\n if (args.name != null) {\n for (const node of resolveTargets({ ...args, to: args.name })) node.textContent = file ? file.name : \"\"\n }\n if (reason) {\n input.dispatchEvent(new CustomEvent(\"reactive:file-rejected\", { bubbles: true, detail: { reason, file: refused } }))\n }\n}\n\n// Which files the page takes: an image by default. accept: true reads the\n// input's own accept attribute; a string is that list for this op. A token is\n// an extension (\".png\"), a MIME type or a type/* wildcard, the browser's own\n// accept grammar. An absent or empty list keeps the image rule.\nfunction previewAccepts(input, accept) {\n const tokens = String((accept === true ? input.getAttribute(\"accept\") : typeof accept === \"string\" && accept) || \"\")\n .toLowerCase()\n .split(\",\")\n .map((token) => token.trim())\n .filter(Boolean)\n if (tokens.length === 0) return (f) => f.type.startsWith(\"image/\")\n return (f) => {\n const type = f.type.toLowerCase()\n return tokens.some((token) =>\n token[0] === \".\"\n ? String(f.name).toLowerCase().endsWith(token)\n : token.endsWith(\"/*\")\n ? type.startsWith(token.slice(0, -1))\n : type === token,\n )\n }\n}\n\nfunction warnPreview(why) {\n console.warn(`[phlex-reactive] preview_file ${why} — skipped`)\n}\n\n// Revoke the URL preview_file made for `img`, if it made one.\nfunction forgetPreview(img) {\n const url = previews.get(img)\n if (url === undefined) return\n previews.delete(img)\n URL.revokeObjectURL(url)\n if (previews.size === 0) {\n previewObserver?.disconnect()\n previewObserver = null\n }\n}\n\nfunction watchPreviews() {\n if (previewObserver || typeof MutationObserver !== \"function\" || !document?.documentElement) return\n previewObserver = new MutationObserver(() => {\n for (const [img, url] of previews) if (!img.isConnected || img.getAttribute(\"src\") !== url) forgetPreview(img)\n })\n previewObserver.observe(document.documentElement, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: [\"src\"],\n })\n}\n\n\n// The set-value contract's dispatch (issue #183): a bubbling `input`, so\n// compute reducers, show bindings and on_complete run as if the user typed.\nfunction dispatchInput(field) {\n if (typeof field.dispatchEvent === \"function\") field.dispatchEvent(new Event(\"input\", { bubbles: true }))\n}\n\n// The body of the set_value op (issue #374). Writes the IDL .value — never the\n// attribute, never HTML — and dispatches `input` only when the field's value\n// actually moved (compared after the write: a textarea normalizes CRLF, a\n// select ends at \"\" for a value no option has). No `change`, no focus. Only an\n// input, textarea or select is a target, so a contenteditable (reactive_editable's\n// to write) warns and skips like any other node. A file input takes \"\" alone —\n// the browser throws on anything else.\nfunction setFieldValue(field, value) {\n if (!/^(input|textarea|select)$/.test(field.localName) || (field.type === \"file\" && value !== \"\")) {\n console.warn(\"[phlex-reactive] set_value skipped: not a field it can write\", field)\n return\n }\n const before = fieldState(field)\n field.value = value\n if (fieldState(field) !== before) dispatchInput(field)\n}\n\n// What set_value compares: .value, or for a <select multiple> its whole\n// selection — assigning .value deselects every other option, which .value\n// (the first selected) alone would not show.\nfunction fieldState(field) {\n return field.type === \"select-multiple\" ? [...field.selectedOptions].map((o) => o.value).join(\"\\n\") : field.value\n}\n\n// Apply a hidden-flag change, optionally animated by a [during, from, to]\n// transition (issue #96). Split out so show/hide/toggle share it.\nfunction setHidden(el, hidden, args) {\n if (args?.transition) runTransition(el, args.transition, () => (el.hidden = hidden))\n else el.hidden = hidden\n}\n\n// show/hide/toggle (issue #271): flip visibility, then mirror the INTENDED\n// state into the `expanded:` target's aria-expanded — computed before the flip,\n// so a transition never delays or races it. The expanded target resolves with\n// the op's own scoping (root-scoped, or document-wide under global: true).\nfunction setVisibility(el, hidden, args, resolveTargets) {\n setHidden(el, hidden, args)\n if (args?.expanded == null || typeof resolveTargets !== \"function\") return\n for (const target of resolveTargets({ ...args, to: args.expanded })) {\n target.setAttribute?.(\"aria-expanded\", String(!hidden))\n }\n}\n\n// The interpret-time attribute guard: refuse (warn + skip) a name off the\n// allowlist. Returns true when the op may proceed.\nfunction guardAttr(name) {\n if (!attrRefused(name)) return true\n console.warn(`[phlex-reactive] refused client attr op on ${JSON.stringify(name)} — skipped`)\n return false\n}\n\n// Copy src, srcset and alt from one <img> onto another (issue #373) — the\n// copy_media op and the reactive_media binding (the bindings feature reaches it\n// through the core handle). Only an <img> target is written, so an iframe,\n// script, object, embed or frame never receives a src from here. A source that\n// is missing or not an <img> clears the target. An <img> source without src is\n// a hit with no image (issue #381): the target loses src and srcset but takes\n// the alt, so a known entry without a logo keeps its name. Each attribute is\n// written only when it changes, so the same URL twice does not reload. Returns\n// the alt copied, false when it cleared, or null when the target was skipped.\nfunction copyMedia(target, source) {\n if (target?.localName !== \"img\") {\n console.warn(`[phlex-reactive] copy_media targets an <img>, not <${target?.localName}> — skipped`)\n return null\n }\n const img = source?.localName === \"img\" ? source : null\n const src = img?.getAttribute(\"src\")\n let changed = false\n for (const name of [\"srcset\", \"src\"]) {\n const value = src ? img.getAttribute(name) : null\n if (value == null) {\n if (target.hasAttribute(name)) changed = target.removeAttribute(name) ?? true\n } else if (target.getAttribute(name) !== value) changed = target.setAttribute(name, value) ?? true\n }\n if (changed) resetImage(target)\n const alt = img?.getAttribute(\"alt\") ?? \"\"\n if (target.getAttribute(\"alt\") !== alt) target.setAttribute(\"alt\", alt)\n return img ? alt : false\n}\n\n// The first focusable descendant of `el`, in document order — the natural\n// keyboard target inside an opened menu/dialog. Covers the standard focusable\n// set; :not([tabindex=\"-1\"]) drops explicitly-removed nodes. Returns null when\n// nothing inside is focusable (focus_first then no-ops).\nconst FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])'\nfunction firstFocusable(el) {\n return el.querySelectorAll?.(FOCUSABLE)?.[0] ?? null\n}\n\n// Parse a [[name, args], ...] op list from a raw attr/param. An array passes\n// through; a JSON string is parsed; anything malformed degrades to [] — a bad\n// ops attr must NEVER break the page (client-side default-deny). Shared by the\n// controller's runOps and the reactive:js stream action (issue #97).\nfunction parseOps(raw) {\n if (Array.isArray(raw)) return raw\n if (typeof raw !== \"string\") return []\n try {\n const list = JSON.parse(raw)\n return Array.isArray(list) ? list : []\n } catch {\n return []\n }\n}\n\n// --- on_client binding records (issue #271) -----------------------------------\n// Each on_client call emits ONE record ({on, ops, window?, outside?, once?,\n// detail?, confirm?, confirmWhen?}); mix space-joins several onto one element (spaces inside a\n// record ride as \\u0020, so the join is unambiguous). Stimulus hands every\n// runOps descriptor on an element the SAME event.params, so runOps selects the\n// record(s) whose descriptor matches the firing event: event.type, the key\n// filter, and window-boundness.\n\n// Stimulus's default keyMappings (letters and digits map to themselves), plus\n// delete and backspace (issue #366): Stimulus lacks those two, so a shortcut\n// naming one binds a bare keydown and only shortcutApplies matches it. A\n// custom Stimulus schema is not mirrored — an unknown filter never matches.\nconst KEY_FILTER_MAP = Object.freeze({\n backspace: \"Backspace\",\n delete: \"Delete\",\n enter: \"Enter\",\n tab: \"Tab\",\n esc: \"Escape\",\n space: \" \",\n up: \"ArrowUp\",\n down: \"ArrowDown\",\n left: \"ArrowLeft\",\n right: \"ArrowRight\",\n home: \"Home\",\n end: \"End\",\n page_up: \"PageUp\",\n page_down: \"PageDown\",\n})\nconst KEY_FILTER_MODIFIERS = Object.freeze([\"meta\", \"ctrl\", \"alt\", \"shift\"])\n\n// Stimulus's rule: the four modifiers must match EXACTLY (ctrl+k is not k);\n// the remaining token is compared to event.key case-insensitively. A\n// non-keyboard event (no event.key) checks the modifiers only. `keyMappings`\n// is the app's Stimulus schema table (custom keys included) when the\n// controller has one; the default table is the fallback.\nfunction keyFilterMatches(filter, event, keyMappings) {\n const parts = filter.split(\"+\")\n for (const mod of KEY_FILTER_MODIFIERS) {\n if (parts.includes(mod) !== Boolean(event[`${mod}Key`])) return false\n }\n const token = parts.find((part) => !KEY_FILTER_MODIFIERS.includes(part))\n if (token === undefined || typeof event.key !== \"string\") return true\n const key =\n keyMappings && Object.hasOwn(keyMappings, token)\n ? keyMappings[token]\n : Object.hasOwn(KEY_FILTER_MAP, token)\n ? KEY_FILTER_MAP[token]\n : /^[a-z0-9]$/.test(token)\n ? token\n : null\n return typeof key === \"string\" && key.toLowerCase() === event.key.toLowerCase()\n}\n\n// Issue #357: a shortcut: (\"mod+z\", or { keys, inEditable }) is a window\n// keydown whose `mod` is meta on Apple platforms and ctrl elsewhere. Ruby\n// renders a meta twin and a ctrl twin; Stimulus filters the key, and this\n// decides whether the twin that fired is this platform's and whether the\n// target is one the user is typing in. Either miss is a complete no-op. A\n// delete/backspace chord (issue #366) has no twins and no Stimulus filter:\n// its bare keydown hears every key, and this check alone picks the chord.\n// (Stimulus typecasts a digit shortcut, \"1\", to a number: hence String().)\nconst shortcutKeys = (spec) => (spec && typeof spec === \"object\" ? spec.keys : String(spec))\n// A shortcut record keeps a bare `on: \"keydown\"`, so bindingMatches lets every\n// keydown through and this check, per record, picks the chord that fired.\nfunction shortcutApplies(spec, event, keyMappings) {\n const target = event.target\n if (spec?.inEditable !== true && (target?.isContentEditable || /^(input|textarea|select)$/.test(target?.localName)))\n return false\n const mod = /[Mm]ac|iP/.test(navigator.userAgentData?.platform ?? navigator.platform) ? \"meta\" : \"ctrl\"\n return keyFilterMatches(shortcutKeys(spec).replace(/^mod\\+/, `${mod}+`), event, keyMappings)\n}\n\n// Parse data-reactive-ops-param into binding records. Stimulus typecasts a\n// lone record to an object; several records stay a string (JSON.parse fails on\n// the join) and are split here. A LEGACY [[op, args]] list (array or JSON\n// string — a hand-built attr) becomes one record flagged `legacy`: it has no\n// `on`, so it always matches, and runOps reads its flags from event.params. A\n// malformed piece warns and is skipped; its siblings still run (default-deny).\nfunction parseBindingRecords(raw) {\n if (Array.isArray(raw)) return [{ ops: raw, legacy: true }]\n if (raw && typeof raw === \"object\") return [raw]\n if (typeof raw !== \"string\") return []\n const text = raw.trim()\n if (text.startsWith(\"[\")) return [{ ops: parseOps(text), legacy: true }]\n const records = []\n for (const piece of text.split(\" \")) {\n if (piece === \"\") continue\n let record = null\n try {\n record = JSON.parse(piece)\n } catch {\n record = null\n }\n if (record && typeof record === \"object\" && !Array.isArray(record)) records.push(record)\n else console.warn(`[phlex-reactive] malformed on_client binding record ${JSON.stringify(piece)} — skipped`)\n }\n return records\n}\n\n// Does this record's descriptor match the firing event? The window check\n// matters for an element carrying both `click` and `click@window`: one inside\n// click reaches both listeners, and only currentTarget tells them apart. It is\n// recognised structurally (isWindow), so an undefined currentTarget is never\n// window-bound and a replay built for another document's window still counts\n// (issue #303). Only a LEGACY record (no `on` by\n// construction) matches everything; any other record without a descriptor is\n// malformed and never matches (default-deny). `keyMappings === null` skips the\n// key filter (the caller's single-candidate shortcut).\nfunction bindingMatches(record, event, keyMappings) {\n if (record.legacy) return true\n const on = record.on\n if (typeof on !== \"string\" || on === \"\") return false\n const dot = on.indexOf(\".\")\n if (event.type !== (dot < 0 ? on : on.slice(0, dot))) return false\n if (Boolean(record.window) !== isWindow(event.currentTarget)) return false\n return dot < 0 || keyMappings === null || keyFilterMatches(on.slice(dot + 1), event, keyMappings)\n}\n\n// Issue #346: a record's `detail` lists top-level keys of event.detail and the\n// JSON scalar each must strictly equal — on_client(\"turbo:submit-end\", ops,\n// detail: { success: true }) runs only after a successful submit. A missing or\n// non-object event.detail, or a missing key, never matches (default-deny); a\n// record without `detail` matches whatever the event carries.\nfunction detailMatches(record, event) {\n const expected = record.detail\n if (expected === undefined) return true\n if (expected === null || typeof expected !== \"object\" || Array.isArray(expected)) return false\n const detail = event.detail\n if (detail === null || typeof detail !== \"object\") return false\n // A missing key reads undefined, which no JSON scalar (null included) equals.\n return Object.keys(expected).every((key) => detail[key] === expected[key])\n}\n\n// A `once: true` record (PR #272) is spent after its first run. Stimulus\n// removes only that binding's own listener; a regular same-event sibling on\n// the element keeps calling runOps with BOTH records, so the spent one is\n// skipped here. Spent state belongs to the TRIGGER element (a WeakMap, so it\n// is collected with it): a morph/stream that swaps in a fresh element starts\n// fresh, and a byte-identical sibling trigger has its own. A window-bound\n// binding's currentTarget is the window, so there the element's ops attr joins\n// the key (a re-rendered window-bound once trigger with the same markup stays\n// spent — a known limit; spentEarlyOnce below has the same one for a replayed\n// `:once` hotkey).\nconst spentOnceBindings = new WeakMap()\nfunction onceBindingSpent(controller, event, record) {\n if (!record.once) return false\n const owner = event.currentTarget ?? controller.element\n let byOwner = spentOnceBindings.get(controller)\n if (!byOwner) {\n byOwner = new WeakMap()\n spentOnceBindings.set(controller, byOwner)\n }\n let spent = byOwner.get(owner)\n if (!spent) {\n spent = new Set()\n byOwner.set(owner, spent)\n }\n const raw = event.params?.ops\n const attr = owner === globalThis.window ? `${typeof raw === \"string\" ? raw : JSON.stringify(raw ?? null)}|` : \"\"\n const key = attr + JSON.stringify(record)\n if (spent.has(key)) return true\n spent.add(key)\n return false\n}\n\n// Two identical descriptors on one element (two mix-ed on_client calls with\n// the same event) are two Stimulus bindings, so runOps runs twice for ONE\n// event. The first call runs every matching record; a repeat is a no-op.\n// \"Repeat\" is (controller, listener target, the element's ops attr): Stimulus\n// walks EVERY binding of one window listener with the SAME event object, so\n// keying on currentTarget alone (= window) would drop every other root's\n// window-bound binding — and a sibling element's within one root. The ops attr\n// is the per-element key (a lone record is typecast to a fresh object per\n// binding, so it is compared by its JSON). Two elements in one root carrying\n// byte-identical window-bound records run once — the same ops, applied once.\nconst ranBindings = new WeakMap()\nfunction bindingsAlreadyRan(event, controller) {\n if (event === null || typeof event !== \"object\") return false\n const raw = event.params?.ops\n const key = typeof raw === \"string\" ? raw : JSON.stringify(raw ?? null)\n let byController = ranBindings.get(event)\n if (!byController) {\n byController = new WeakMap()\n ranBindings.set(event, byController)\n }\n let byTarget = byController.get(controller)\n if (!byTarget) {\n byTarget = new Map()\n byController.set(controller, byTarget)\n }\n let keys = byTarget.get(event.currentTarget)\n if (!keys) {\n keys = new Set()\n byTarget.set(event.currentTarget, keys)\n }\n if (keys.has(key)) return true\n keys.add(key)\n return false\n}\n\n// Interpret a [[name, args], ...] op list against the frozen CLIENT_OPS\n// whitelist (issues #95/#96/#97). `resolveTargets(args)` returns the element(s)\n// an op applies to — the controller scopes to its root (excluding nested\n// reactive roots); the reactive:js stream action scopes to its target root (or\n// the document). An unknown name warns and is SKIPPED while the rest of the\n// chain still applies — client-side default-deny, one bad op never takes down\n// its siblings. Object.hasOwn (not a bare read) so inherited Object members\n// (\"constructor\") can't masquerade as ops.\nfunction applyOps(list, resolveTargets, onZeroTargets) {\n for (const entry of list) {\n if (!Array.isArray(entry)) continue\n const [name, args = {}] = entry\n if (!Object.hasOwn(CLIENT_OPS, name)) {\n console.warn(`[phlex-reactive] unknown client op ${JSON.stringify(name)} — skipped`)\n continue\n }\n const targets = resolveTargets(args)\n if (targets.length === 0 && onZeroTargets) onZeroTargets(name, args)\n for (const el of targets) {\n CLIENT_OPS[name](el, args, resolveTargets)\n if (args?.keep === true) keepResult(el, name, args, resolveTargets)\n }\n }\n}\n\n// Resolve a reactive:js op's targets against its `target` root (issue #97).\n// \"@root\" is the root element itself; a selector resolves WITHIN it; a bare\n// selector with no root (no `target` attr on the stream) resolves document-wide\n// — a broadcast op anchored by a global selector (#bell) rather than a\n// component. Unlike the controller path there is no nested-reactive-root\n// ownership filter: a server-pushed op names its own scope explicitly —\n// including `global: true`, which opts a single op out of the target-root\n// scope to document-wide resolution (issue #159; the same escape the builder\n// documents for the controller path).\nfunction streamOpTargets(args, root) {\n const to = args.to\n if (root) {\n if (to === \"@root\") return [root]\n if (typeof to !== \"string\" || to === \"\") return []\n if (args.global) return [...document.querySelectorAll(to)]\n return [...root.querySelectorAll(to)]\n }\n // No target root: document-scoped. \"@root\" is meaningless here (nothing to\n // anchor to) → no-op; a selector matches document-wide.\n if (typeof to !== \"string\" || to === \"\" || to === \"@root\") return []\n return [...document.querySelectorAll(to)]\n}\n\n// --- Feature modules (issue #275) ---------------------------------------------\n// The client is a small core plus feature modules a page loads only when one\n// of its roots asks for them. Each entry is [needs, load, waiting?, gates?]:\n//\n// needs(root) a cheap marker read — does this root use the feature?\n// null for a feature no root asks for (it is loaded by\n// something page-level instead).\n// load() how to import the module — NOT written here. core.js,\n// the opt-in entry, supplies one per feature\n// (registerReactiveFeatureLoader): a LITERAL\n// import(\"phlex/reactive/features/<name>\"), so a bundler can\n// see the specifier and the import map resolves it to its\n// own digested file. The default entry supplies none: its\n// features are part of the file.\n// waiting(root, pending)\n// optional, and like load() supplied by core.js (only the\n// opt-in client ever waits): what must not be missed WHILE\n// the import is on its way. Runs when a root starts waiting, RECORDS into\n// `pending` (a plain object the feature receives later) and\n// returns its own undo. Core bytes — record, never act.\n// gates optional, true when the feature's connect changes what a\n// REQUEST reads (the draft restore writes the fields a\n// request collects). A root's action requests wait for its\n// features only while one that gates is still loading; a\n// feature that does not gate never delays a request.\n//\n// TABLE ORDER IS CONNECT ORDER (and disconnect order): persist is first\n// because its restore writes the values every other connect-time seed reads.\n//\n// A feature module exports\n//\n// install(shared) optional; once, when it has loaded\n// connect(controller, core, morphed, pending)\n// once per root connection; `pending` is what\n// its `waiting` hook recorded (undefined when\n// the root never had to wait)\n// disconnect(controller, core)\n// FIRST in the controller's disconnect()\n// abandon(root, pending) optional; the root left BEFORE the feature\n// could connect — last chance to act on\n// what was recorded\n//\n// and never imports this file (two copies of the core would split its state).\n// `shared` is FEATURE_SHARED below: the core's single-instance counters.\n// Everything a feature needs from one controller arrives as `core`, the\n// handle #featureCore builds — the ONE door into its private state:\n//\n// core.emit(name, detail, options) raw-dispatch a lifecycle event\n// core.reseed() re-run the connect-time seeds that\n// read field values\n// core.proceed(target, action, params) send an action down the ordinary\n// pipeline (veto, queue, request)\n// core.forgetToken() drop the token cached from a reply\n// core.owns(el) is this control this root's own, not\n// a nested reactive root's (issue #15)\n// core.ownership() the hoisted per-pass form of owns\n// core.opTargets(args) the elements an op's `to:` names\n// core.applyOps(list, defaultTo) run an op list root-scoped\n// core.diagnose(label, args) the zero-target warning\n// core.listnavOptions(event) the keyboard-navigable options\n// core.collectFields() this root's fields, as a request\n// would send them\n// core.confirm(message, context) the confirm gate (resolves a boolean)\n//\n// Later features add to that handle; nothing else of the controller is theirs\n// to touch beyond its public surface (element, application, …).\n//\n// WHEN A FEATURE CONNECTS. A module that is already here connects inside\n// connect() itself, in the same task, before the early-queue drain. In the\n// default bundle (phlex/reactive/reactive_controller) that is every feature,\n// always: they are handed over before anything connects, and nothing below\n// about imports, waiting, gates or timeouts ever applies.\n//\n// With phlex/reactive/core alone, the first root on a page that needs a\n// feature waits for its import: connect() only starts it, and the feature\n// connects when the module has arrived — after every feature EARLIER in the\n// table that this root is also waiting for (table order), never after a later\n// one: a slow defer import does not hold the draft restore back. Once the\n// module is loaded (a later root, the next Turbo visit, a reconnect) it too\n// connects inside connect().\n//\n// THE DEFAULT ENTRY HAS NONE OF THIS (issue #305). It is built with\n// `__SPLIT__` false: the runtime imports every feature statically and calls\n// it directly — connect() connects what the root's markers ask for, in this\n// order, by name in the source and by nothing at all in the minified file.\n// No table, no loader, no import(): what is below is the opt-in entry's.\n\n// The marker checks of the features that connect per root. The default entry\n// calls them directly (#connectFeaturesNow); the table below hands them to\n// the loader.\nfunction persistNeeded(root) {\n const declared = root.getAttribute?.(\"data-reactive-persist\")\n return Boolean(declared) && declared !== \"off\"\n}\n\n// reply.defer, reactive_lazy, reactive_lazy(on:/cache:). A root is a lazy\n// shell when it carries any of these; real content carries none.\nfunction deferNeeded(root) {\n return [\"defer-token\", \"defer-src\", \"lazy-on\", \"lazy-visible\"].some(\n (marker) => root.getAttribute?.(`data-reactive-${marker}`) != null,\n )\n}\n\n// Issue #410: a tokenless CLIENT-ONLY root (a ClientBindings component: filter,\n// show, on_client) has no action of its own. It is transparent to server round\n// trips: a server trigger inside it dispatches on the nearest token-bearing root\n// above, and that root's #collectFields looks through it. A lazy or deferred\n// shell is tokenless by design and is NOT client-only.\nconst AWAKE_ROOT = '[data-controller~=\"reactive\"]'\nconst ANY_ROOT = '[data-controller~=\"reactive\"],[data-reactive-dormant~=\"reactive\"]'\n\nconst clientOnlyRoot = (root) => root.getAttribute?.(\"data-reactive-token-value\") == null && !deferNeeded(root)\n\n// The first `selector` root at or above `from` that is not client-only, or\n// `stop` when the walk reaches it first, or null.\nfunction serverRoot(from, selector, stop) {\n let root = from?.closest?.(selector)\n while (root && root !== stop && clientOnlyRoot(root)) root = root.parentElement?.closest?.(selector)\n return root ?? null\n}\n\n// Connected controllers by root element, so a client-only root can hand a\n// dispatch to the server root above it. A dispatch for a root that has not\n// connected yet (a dormant root being woken) waits here until it does.\nconst rootControllers = new WeakMap()\nconst heldForwards = new WeakMap()\n\n// Dirty tracking with its navigate-away guard, and the paste-trigger gate:\n// the root or anything inside it carries one of the two markers. (A nested\n// root's marker loads the module for its parent too; the module then checks\n// ownership before it wires anything.)\nfunction formNeeded(root) {\n return (\n (root.getAttribute?.(\"data-action\") ?? \"\").includes(\"reactive#trackDirty\") ||\n root.getAttribute?.(\"data-reactive-clipboard\") != null ||\n root.querySelector?.('[data-action*=\"reactive#trackDirty\"],[data-reactive-clipboard]') != null\n )\n}\n\n// Show bindings and cross-root show targets, completion bindings, option\n// filtering, the bulk-selection group bindings (enable, select-all, count —\n// issue #319) and their cross-root targets (#343), the tag-chip input, draft\n// nested rows, editable text's keys and paste (#351), media previews\n// (#373) and fields fed into CSS custom properties (#417): a form's\n// client-only bindings. The root declares one, or owns an element that does.\n// (The probes the connect-time gates ran before the split, unchanged.)\nfunction bindingsNeeded(root) {\n return (\n [\"show-targets\", \"group-targets\", \"on-complete\", \"filter-input\", \"tags-field\", \"css-vars\"].some(\n (marker) => root.getAttribute?.(`data-reactive-${marker}`) != null,\n ) ||\n (root.querySelectorAll?.(\"[data-reactive-show-field], [data-reactive-show]\") ?? []).length > 0 ||\n root.querySelector?.(\n \"[data-reactive-nested-json], [data-reactive-nested-list], [data-reactive-confirm-when-param], [data-reactive-enable], [data-reactive-select-all], [data-reactive-count], [data-reactive-editable], [data-reactive-media], [data-reactive-css-vars]\",\n ) != null\n )\n}\n\n// Client-side computes: the root carries a reactive_compute binding.\nfunction computeNeeded(root) {\n return root.getAttribute?.(\"data-reactive-compute-inputs-param\") != null\n}\n\n// Sortable lists (issue #350): the root is, or holds, a sortable list or a\n// source list. (A nested root's list loads the module for its parent too; the\n// module then checks ownership before it picks a row up.)\nfunction sortableNeeded(root) {\n const lists = \"[data-reactive-sortable],[data-reactive-sortable-source]\"\n return Boolean(root.matches?.(lists) || root.querySelector?.(lists))\n}\n\n// Draggable regions (issue #416): the root is, or holds, a frame, a box or a\n// point. (A nested root's frame loads the module for its parent too; the\n// module then checks ownership before it drags anything.)\nfunction dragNeeded(root) {\n const marks = \"[data-reactive-frame],[data-reactive-box],[data-reactive-point]\"\n return Boolean(root.matches?.(marks) || root.querySelector?.(marks))\n}\n\n// Presence (issue #358): the root is a reactive_presence roster.\nfunction presenceNeeded(root) {\n return root.getAttribute?.(\"data-reactive-presence\") != null\n}\n\n// Countdowns (issue #400): the root is, or holds, a reactive_countdown.\nfunction countdownNeeded(root) {\n return Boolean(root.matches?.(\"[data-reactive-countdown]\") || root.querySelector?.(\"[data-reactive-countdown]\"))\n}\n\nconst PRODUCTION_FEATURES = [\n // (Its `waiting` hook is the opt-in entry's: core.js.) The draft restore\n // writes what a request reads, so it gates.\n [\"persist\", [persistNeeded, null, null, true]],\n [\"defer\", [deferNeeded, null]],\n [\"form\", [formNeeded, null]],\n // Its seeds write what a request reads (the JSON-mode rows), so it gates.\n [\"bindings\", [bindingsNeeded, null, null, true]],\n // The seed writes output fields a request collects, so it gates.\n [\"compute\", [computeNeeded, null, null, true]],\n // Nothing it does at connect is read by a request: it never gates.\n [\"sortable\", [sortableNeeded, null]],\n // A drag writes fields only when the user drags: it never gates.\n [\"drag\", [dragNeeded, null]],\n // Its announce is a request of its own; nothing it does at connect is read\n // by another request: it never gates.\n [\"presence\", [presenceNeeded, null]],\n // A countdown writes only its own text and visibility: it never gates.\n [\"countdown\", [countdownNeeded, null]],\n [\n // Stream effects and dismissing flashes are document-level: the module\n // has no connect. A root that DECLARES an effect only gets it fetched\n // early, so the first stream need not wait for it (see streamHold).\n \"effects\",\n [\n (root) => [\"enter\", \"update\", \"exit\"].some((hook) => root.getAttribute?.(`data-reactive-effect-${hook}`) != null),\n null,\n ],\n ],\n [\n // The optimistic/busy hint engine: a trigger in the root declares a hint.\n // No connect — the module acts at enqueue — so the marker is only read\n // while it is not loaded, and a root that declares one preloads it.\n \"hints\",\n [\n (root) =>\n root.querySelector?.(\n \"[data-reactive-optimistic-param], [data-reactive-busy-param], [data-reactive-loading-param]\",\n ) != null,\n null,\n ],\n ],\n [\n // Development aids: the latency simulator (page-level, see\n // registerReactiveDev), the zero-target diagnostics and the debug trace.\n // A root in debug mode preloads it; a verbose root asks for it the first\n // time it has something to warn about.\n \"devtools\",\n [(root) => root.getAttribute?.(\"data-reactive-debug\") === \"true\", null],\n ],\n]\n// (Only the opt-in entry has a table: `new Map` would keep it in the default.)\nconst FEATURES = __SPLIT__ ? new Map(PRODUCTION_FEATURES) : null\n\n// name -> the one import promise every root shares, kept even when it\n// rejected: a browser caches a module that failed to load, so importing it\n// again only re-rejects. A failed feature stays failed until the page is\n// reloaded; every root it costs is told (reactive:error).\nconst featureLoads = __SPLIT__ ? new Map() : null\n// name -> the loaded module: what lets a later root connect a feature inside\n// connect(), and a stream action or client op use it in the same tick.\nconst featureModules = __SPLIT__ ? new Map() : null\n// The modules the default bundle handed over (registerReactiveFeature); only\n// the test seam below reads it back.\nconst featuresGiven = __SPLIT__ ? new Map() : null\n\n// How the opt-in entry (core.js) tells the runtime where a feature module is\n// — `load`, a function returning its import() — and, optionally, what to\n// record while that import is on its way (`waiting`, see above).\nexport function registerReactiveFeatureLoader(name, load, waiting) {\n if (__SPLIT__) {\n featureLoaders.set(name, [load, waiting])\n applyFeatureLoader(name)\n }\n}\n\n// name -> [load, waiting] the opt-in entry registered: kept apart from the\n// table so a test's reset of the table puts them back.\nconst featureLoaders = __SPLIT__ ? new Map() : null\n\nfunction applyFeatureLoader(name) {\n const entry = FEATURES.get(name)\n const [load, waiting] = featureLoaders.get(name)\n entry[1] = load\n entry[2] = waiting\n}\n\n// Hand the runtime a feature that is ALREADY loaded — what\n// phlex/reactive/reactive_controller does for every feature before anything\n// can connect. Such a feature is never imported: it connects inside connect().\nexport function registerReactiveFeature(name, feature) {\n if (__SPLIT__) {\n feature.install?.(FEATURE_SHARED)\n featureModules.set(name, feature)\n featuresGiven.set(name, feature)\n }\n}\n\n// For the opt-in entry: the features some of `elements` (reactive roots a\n// stream is about to render) need and that are not loaded — read off the\n// table, so a new feature is covered by its own marker check.\nexport function unloadedFeaturesFor(elements) {\n const names = []\n if (__SPLIT__) {\n for (const [name, [needs]] of FEATURES) {\n if (!needs || featureModules.has(name)) continue\n try {\n if (elements.some((el) => needs(el))) names.push(name)\n } catch {\n // a marker check that throws is that root's problem at connect, not the stream's\n }\n }\n }\n return names\n}\n\n// For the opt-in entry: import a feature now. Resolves with the module, or\n// with undefined once the failure has been logged.\nexport function loadReactiveFeature(name) {\n return __SPLIT__\n ? loadFeature(name).catch((error) => logFeatureFailure(name, \"load\", error))\n : Promise.resolve()\n}\n\nfunction loadFeature(name) {\n let loading = featureLoads.get(name)\n if (!loading) {\n const load = FEATURES.get(name)[1] ?? (() => Promise.reject(new Error(\"this entry has no way to import it\")))\n loading = load().then((feature) => {\n feature.install?.(FEATURE_SHARED)\n featureModules.set(name, feature)\n return feature\n })\n featureLoads.set(name, loading)\n }\n return loading\n}\n\n// Logged once per feature and phase per page (a failed import would otherwise\n// log again for every root that connects). `phase` says what went wrong:\n// \"detect\" (its marker check threw), \"load\" (the import failed — and stays\n// failed until the page is reloaded), \"timeout\" (slow; the root carried on\n// without it) or \"connect\" (it threw while wiring a root).\nconst featureFailuresLogged = new Set()\n\nfunction logFeatureFailure(name, phase, error) {\n const logged = name + phase\n if (featureFailuresLogged.has(logged)) return\n featureFailuresLogged.add(logged)\n console.error(\n `[phlex-reactive] the \"${name}\" feature module (phlex/reactive/features/${name}) is unavailable: ${phase}. ` +\n \"If you bundle or vendor the client, alias or pin phlex/reactive/features/* (README: esbuild / webpack / bun).\",\n error,\n )\n}\n\n// Run `use(feature)` with a feature — for code with no root connection to\n// wait on (a client op, a stream action): in this tick when the module is\n// loaded, else once it has arrived. A feature that cannot load is logged.\nfunction withFeature(name, use) {\n const feature = featureModules.get(name)\n if (feature) return use(feature)\n loadFeature(name).then(use, (error) => logFeatureFailure(name, \"load\", error))\n}\n\n// How long a root waits for a feature import before it carries on without it\n// (<meta name=\"phlex-reactive-feature-timeout\" content=\"ms\"> in <head>).\n// 10 s: the modules are a few KB, so even a slow 3G fetch (seconds, not tens\n// of seconds) finishes well inside it, and it matches the early-event TTL —\n// the other \"how long may the client be late\" window. A feature that arrives\n// AFTER the timeout still connects; the timeout only stops it holding back\n// the root's other features and its action requests.\nconst DEFAULT_FEATURE_TIMEOUT_MS = 10000\n\nfunction featureTimeoutMs() {\n const ms = Number(globalThis.document?.head?.querySelector?.('meta[name=\"phlex-reactive-feature-timeout\"]')?.content)\n return Number.isFinite(ms) && ms > 0 ? ms : DEFAULT_FEATURE_TIMEOUT_MS\n}\n\nexport function reactiveFeatureNames() {\n return __SPLIT__\n ? [...FEATURES.keys()]\n : [\"persist\", \"defer\", \"form\", \"bindings\", \"compute\", \"sortable\", \"drag\", \"presence\", \"countdown\", \"effects\", \"hints\", \"devtools\"]\n}\n\n\n// Test-only: a feature's table entry, to wrap one part of it.\n\n// Test-only: import a feature now (a later connect is then synchronous).\n\n// Test-only: back to the shipped table, with nothing logged and only the\n// modules the default bundle handed over still loaded — or, `cold`, with none\n// loaded at all: the state of a page that imported phlex/reactive/core.\n\nconst FEATURES_READY = Promise.resolve()\n\n// The single-instance module state a feature may add to (install()): ONE\n// in-flight activity count and one request total per page, whoever counts.\n// `waiting` maps a root to { <feature name>: pending } while it waits for that\n// feature's import (see the `waiting` hook above).\nfunction forgetWaiting(root, name, pending) {\n const waiting = FEATURE_SHARED.waiting.get(root)\n // (A root that reconnected meanwhile has a NEW record under the same name.)\n if (waiting && waiting[name] === pending) delete waiting[name]\n}\n\nconst FEATURE_SHARED = {\n enter: enterReactiveActivity,\n exit: exitReactiveActivity,\n count: countReactiveRequest,\n // The editable collector and server text (issue #351), for the draft.\n text: editableValue,\n served: serverText,\n waiting: new WeakMap(),\n}\n\n// The default entry's per-root features, one bit each (#connectFeaturesNow).\nconst PERSIST = 1\nconst DEFER = 2\nconst FORM = 4\nconst BINDINGS = 8\nconst COMPUTE = 16\nconst SORTABLE = 32\nconst PRESENCE = 64\nconst COUNTDOWN = 128\nconst DRAG = 256\n\n// The default entry's features are part of this file: install them now, as\n// the opt-in entry does when one arrives.\nif (!__SPLIT__) {\n persistFeature.install(FEATURE_SHARED)\n deferFeature.install(FEATURE_SHARED)\n}\n\n// --- Early triggers (issue #273) ----------------------------------------------\n// phlex/reactive/early (imported eagerly by the app) queues trigger events that\n// reach a root before its controller connects. The queue is shared through a\n// Symbol.for key on window — no import edge either way, so either module may\n// load first, and an app that never imports early gets an empty queue.\nconst EARLY_KEY = Symbol.for(\"phlex-reactive.early\")\nconst DEFAULT_EARLY_TTL_MS = 10000\n// A window-bound trigger (a hotkey, issue #303) is replayed only this soon\n// after the keypress: later, the user has moved on. A shorter configured TTL\n// still wins.\nconst WINDOW_EARLY_TTL_MS = 1500\n\n// Is this event's currentTarget a Window (a Stimulus `@window` listener)? The\n// page's window, or any Window recognised structurally (another document's,\n// which a replay built from its trigger element's document carries).\nconst isWindow = (target) => target != null && (target === globalThis.window || target.window === target)\n\n// Issue #393: the key on()'s dispatch debounce uses in #debounceTimers. An\n// on_client record keys on its JSON, which starts with \"{\", so never this.\nconst DISPATCH_WAIT_KEY = \"dispatch\"\n\nfunction earlyState() {\n globalThis[EARLY_KEY] ??= { queue: [], connected: new WeakSet() }\n return globalThis[EARLY_KEY]\n}\n\n// <meta name=\"phlex-reactive-early-ttl\" content=\"ms\"> (Phlex::Reactive.\n// early_event_ttl_ms), parsed defensively like the timeout meta.\nfunction earlyTtlMs() {\n const ms = Number(globalThis.document?.querySelector?.('meta[name=\"phlex-reactive-early-ttl\"]')?.content)\n return Number.isFinite(ms) && ms > 0 ? ms : DEFAULT_EARLY_TTL_MS\n}\n\n// Stimulus's param reader (data-reactive-<name>-param, JSON-typecast,\n// camelized) — a replayed trigger carries the same event.params a live one does.\nfunction stimulusParams(el) {\n const params = {}\n for (const { name, value } of Array.from(el.attributes ?? [])) {\n const match = /^data-reactive-(.+)-param$/i.exec(name)\n if (!match) continue\n let typed\n try {\n typed = JSON.parse(value)\n } catch {\n typed = value\n }\n params[match[1].replace(/[-_]([a-z0-9])/g, (_, char) => char.toUpperCase())] = typed\n }\n return params\n}\n\n// The event a replay hands to dispatch()/runOps(). It is called DIRECTLY, not\n// re-dispatched on the element: a real event would also re-run every OTHER\n// listener on the trigger (a lazily connected sibling controller that already\n// handled the original click would toggle twice). The original's default was\n// already prevented (or deliberately kept) by early.js, so preventDefault here\n// is a no-op. A window-bound entry (issue #303) is replayed as Stimulus's\n// window listener sees it: currentTarget is the window, params come from `el`.\nfunction earlyReplayEvent(event, el, win) {\n return {\n [EARLY_KEY]: true,\n type: event.type,\n detail: event.detail,\n target: event.target,\n currentTarget: win ? el.ownerDocument.defaultView : el,\n params: stimulusParams(el),\n key: event.key,\n code: event.code,\n metaKey: event.metaKey,\n ctrlKey: event.ctrlKey,\n altKey: event.altKey,\n shiftKey: event.shiftKey,\n button: event.button,\n submitter: event.submitter,\n bubbles: event.bubbles,\n timeStamp: event.timeStamp,\n defaultPrevented: event.defaultPrevented,\n preventDefault() {},\n stopPropagation() {},\n stopImmediatePropagation() {},\n }\n}\n\n// A :once trigger that was REPLAYED is spent, but Stimulus's own `once`\n// listener for it is still armed (the replay calls the method directly). The\n// markup is left alone — rewriting data-action would let a morph, which writes\n// the server's attribute back, re-arm it — so the spent descriptor is tracked\n// per trigger element (a WeakMap: a replaced element starts fresh, like a live\n// `once`), and the armed listener's single firing is swallowed. A window-bound\n// descriptor's live call carries the window as currentTarget, not its\n// element, so it is tracked under the ROOT instead (issue #303), flagged\n// `win` (apart from the root's own element-bound descriptors) and told apart\n// from its siblings by the params Stimulus reads off its element.\n//\n// Known limit of the root-keyed entry (the same class as onceBindingSpent's\n// window-bound limit above): a reply that MORPHS the root but swaps the\n// trigger element gives Stimulus a fresh `once` listener for the new element\n// while this entry stays armed, so the next live press is swallowed here and\n// that fresh listener is consumed — the hotkey is dead until the root\n// disconnects. Keep a `:once` hotkey's trigger element stable across replies,\n// or bind the hotkey without `once:`.\nconst spentEarlyOnce = new WeakMap()\n\nfunction spendEarlyOnce(owner, { token, type, method, filter, win }, params) {\n let spent = spentEarlyOnce.get(owner)\n if (!spent) spentEarlyOnce.set(owner, (spent = new Map()))\n const key = win ? `${token} ${params}` : token\n if (spent.has(key)) return false\n spent.set(key, { type, method, filter, win, params, armed: true })\n return true\n}\n\n// True (once) for the live call that comes from a spent descriptor's listener:\n// same element, method, event type AND key filter — a sibling descriptor with\n// another filter (keydown.esc beside a spent keydown.enter:once) is not it.\n//\n// Also true for the ORIGINAL of a replayed event arriving live at the same\n// element (issue #274): waking a dormant root can connect its controller while\n// the waking event is still propagating (an eagerly registered controller\n// connects in the microtask checkpoint after early.js's capture listener), so\n// the event reaches the listener Stimulus just bound after it was replayed.\n// Only as many live calls as the replay RAN for that element and method are\n// dropped — a binding the replay skipped (a key filter only the app's own\n// mapping matches) still runs.\nfunction earlyOnceSwallows(event, method, keyMappings, root) {\n if (event?.[EARLY_KEY]) return false\n const win = isWindow(event?.currentTarget)\n // A window-bound replay is owned by its ROOT: another root's binding on the\n // same window event is not it (issue #303).\n const owner = win ? root : event?.currentTarget\n const replayed = takeEarlyReplay(event, owner, win ? `@${method}` : method)\n const spent = spentEarlyOnce.get(owner)\n const params = win && JSON.stringify(event.params)\n for (const entry of spent?.values() ?? []) {\n if (!entry.armed || entry.method !== method || entry.type !== event.type || Boolean(entry.win) !== win) continue\n if (win && entry.params !== params) continue\n if (entry.filter && !keyFilterMatches(entry.filter, event, keyMappings)) continue\n entry.armed = false\n return true\n }\n return replayed\n}\n\n// event → (element → { dispatch: n, runOps: n }): how many bindings a replay\n// ran while its original may still be propagating. The entry lasts for that\n// propagation only — a new task starts after the whole dispatch is over — so\n// the same event OBJECT dispatched again later is live.\nconst replayedEarly = new WeakMap()\n\nfunction markEarlyReplay(event, el, method) {\n let byElement = replayedEarly.get(event)\n if (!byElement) {\n replayedEarly.set(event, (byElement = new Map()))\n setTimeout(() => replayedEarly.delete(event))\n }\n const counts = byElement.get(el) ?? {}\n byElement.set(el, counts)\n counts[method] = (counts[method] ?? 0) + 1\n}\n\nfunction takeEarlyReplay(event, owner, method) {\n const counts = event && replayedEarly.get(event)?.get(owner)\n if (!counts?.[method]) return false\n counts[method] -= 1\n return true\n}\n\n// Register this controller eagerly OR lazily: with phlex/reactive/early\n// imported, a trigger that fires before connect is replayed on connect (issue\n// #273). The engine auto-pins it with preload: true for importmap apps; see\n// the README for esbuild/webpack.\nexport default class extends Controller {\n static values = {\n token: String, // signed identity token (component + record gid/state)\n }\n\n #tokenCache // freshest token, threaded synchronously across queued requests\n #debounceTimers = new Map() // trigger element -> Map(key -> { timer, flush }) pending run\n #throttleTimers = new Map() // trigger element -> Map(action or record key -> suppression timer)\n #actionPathCache // page-stable action path, resolved once per controller\n #timeoutMsCache // page-stable request timeout (ms), resolved once per controller (issue #101)\n #tokenRegexCache // { id, token, self } — #extractToken's two per-id RegExps, rebuilt on id change (issue #118)\n // Loading-state bookkeeping (issue #99). All keyed so overlapping enqueues\n // refcount correctly and never clobber each other:\n #busyPending = 0 // root aria-busy pending counter (remove only at zero)\n #busyActions = new Map() // action -> in-flight count (root's space-separated busy set + busy_on)\n #busyTokenCounts = new WeakMap() // element -> Map(action -> count): its data-reactive-busy token set\n // Dirty tracking (issue #103): the bound re-scan (turbo:morph-element) and the\n // navigate-away guard handlers, held so disconnect() can remove exactly them.\n // The root-only morph listener of a token root or lazy shell (#275, #312): a morph\n // can add a feature's marker to a connected root. Held for teardown.\n #boundRootMorph\n // Clipboard-trigger availability gate (issue #228): the bound morph re-sync,\n // held for teardown.\n // Client-only drafts (issue #239): the parsed root payload (null when\n // undeclared), the restore-complete latch (no write may run before the\n // connect restore — a connect must never overwrite a draft with server\n // blanks), the ONE per-root trailing-edge write timer, and the bound\n // input/change/turbo:submit-end handlers held for teardown.\n // Feature modules (issue #275). `featuresReady` resolves once the features\n // this root needs have connected (or been given up on: failed, or slower\n // than the timeout) — it never rejects, and is already resolved for a root\n // that needs none. connect() only STARTS the imports. The name is long on\n // purpose: it is a public field, and a short `ready` would shadow a method\n // an app's own subclass may well define.\n // #featureEpoch changes on every connect and disconnect, so an import that\n // resolves for a connection that has since ended connects nothing.\n featuresReady = FEATURES_READY\n #features = new Map() // name -> module connected on this connection\n #featuresWanted = new Set() // names this connection has asked for\n #featuresSettling = false // true while featuresReady is still pending\n #featureGate = null // settles when the last feature that `gates` has connected\n #featureWaits = new Map() // pending import-timeout timer -> its wait's resolve\n #featureHooks = new Map() // name -> [undo, pending] of a feature still on its way\n #featureTurns = new Map() // name -> settles once that feature has had its turn to connect\n #featureEpoch = 0\n #featureHandle // the `core` handle features receive, built on first use\n // The default entry (issue #305): one bit per feature, PERSIST … COUNTDOWN —\n // asked for (its marker read true) and connected on this connection.\n #featuresAsked = 0\n #featuresOn = 0\n\n // Mark that a reactive controller actually connected, so the registration\n // guard above knows the controller was registered (issue #26 part 2).\n connect() {\n reactiveConnected = true\n // Early triggers (issue #273): from here on Stimulus delivers this root's\n // events live (its bindings are already wired), so early.js must stop\n // queueing them NOW — a connect-time seed below (a compute output write)\n // dispatches real input events. The queue itself drains at the very end.\n earlyState().connected.add(this.element)\n rootControllers.set(this.element, this)\n\n // Root-id guard (issue #48). The token round trip assumes the reactive root\n // element's id == component.id: the server targets component.id and the client\n // self-matches its NEXT token by this.element.id (#extractToken, issue #46).\n // If `id:` landed on a CHILD instead of the `**reactive_attrs` root, this id is\n // \"\" — #extractToken falls back to the FIRST token in the response (a child's),\n // so the next action POSTs a foreign token → endpoint default-deny → silent 403.\n // Warn NOW (on connect) so the failure surfaces on page load, not on click 2.\n if (this.element.id === \"\") {\n console.warn(\n \"[phlex-reactive] a reactive root has no id; its next-action token can't self-match \" +\n \"and may fall back to the first token in the response → a silent HTTP 403 on the NEXT action. \" +\n \"Put id: on the SAME element as reactive_attrs — use div(**reactive_root) (emits id + token together), \" +\n \"or div(id:, **reactive_attrs). The id: must NOT be on a child. See the README.\"\n )\n }\n\n // Lazy shells (reactive_lazy, on:, cache:) are wired by the defer feature\n // module (issue #275), which #loadFeatures below starts importing when the\n // root carries one of its markers. What stays here is the root-only morph\n // listener of a TOKEN-BEARING root or a LAZY SHELL: a Turbo morph can turn\n // a root that connected on REAL content (no marker, so no feature) into a\n // shell while it stays connected — no Stimulus lifecycle fires — and only\n // a listener that is already there can notice and load the feature. A\n // shell carries no identity token (a `cache:` one has its URL, issue #306;\n // a plain one its defer token), and a morph can turn it, still connected,\n // into a root that needs another feature (issue #312). It costs a root\n // without any marker, before and after the morph, a few attribute reads and\n // never an import. (turbo:morph-element BUBBLES: only a morph of the root\n // itself counts.) A tokenless, client-only root has no such listener.\n // The same listener hears an editable being focused (issue #351): a stream\n // can append a root's first reactive_editable without morphing the root,\n // and its keys and paste need the bindings feature before anything is\n // typed into it. And it hears a stream that rendered into the root\n // (STREAMED, issue #356): the features already connected re-run their\n // seeds first (a morph's own listeners do that for a morph), then what the\n // new content needs connects.\n if (this.element.getAttribute?.(\"data-reactive-token-value\") != null || deferNeeded(this.element)) {\n this.#boundRootMorph = (event) => {\n if (event.type === \"focusin\" ? !event.target.hasAttribute?.(\"data-reactive-editable\") : event.target !== this.element) return\n if (event.type === STREAMED) this.#reseed()\n if (__SPLIT__) this.#loadFeatures(true)\n else this.#connectFeaturesNow(true)\n }\n this.element.addEventListener?.(\"turbo:morph-element\", this.#boundRootMorph)\n this.element.addEventListener?.(\"focusin\", this.#boundRootMorph)\n this.element.addEventListener?.(STREAMED, this.#boundRootMorph)\n }\n\n // Feature modules (issue #275): connect the ones that are here and START\n // the import of any that is not. HERE, before the seeds below, because\n // this is where their work used to sit: the draft restore (persist) writes\n // the values those seeds read, the dirty baseline (form) comes before\n // them. A module that is still on its way connects when it arrives and\n // re-runs the seeds itself (#reseed). Never awaited — connect() must reach\n // the drain at the end in the same task (issue #274: an await before it\n // runs a waking click twice).\n // The editables' server texts (issue #351), before a draft restore\n // writes into them.\n this.element.querySelectorAll?.(\"[data-reactive-editable]\").forEach(seedServerText)\n\n this.#featureEpoch++\n this.featuresReady = FEATURES_READY\n if (__SPLIT__) this.#loadFeatures()\n else this.#connectFeaturesNow()\n\n // A dialog the server rendered open with data-reactive-modal (issue #365).\n upgradeModals(this.element)\n\n // LAST, after every feature above is wired: a replayed trigger must find\n // the controller exactly as a live event after connect would.\n this.#announceConnected()\n }\n\n // The default entry (issue #305): every feature is in this file, so each\n // one this root's markers ask for connects now, by a direct call, in the\n // order the opt-in entry's table gives (persist first: its restore writes\n // the values the later seeds read). Again after a morph of the root, for a\n // marker the morph added. The features with no per-root connect (effects,\n // hints, devtools) act when called.\n #connectFeaturesNow(morphed) {\n if (!__SPLIT__) {\n this.#connectFeatureNow(PERSIST, \"persist\", persistNeeded, (core) => persistFeature.connect(this, core, morphed))\n this.#connectFeatureNow(DEFER, \"defer\", deferNeeded, (core) => deferFeature.connect(this, core, morphed))\n this.#connectFeatureNow(FORM, \"form\", formNeeded, (core) => formFeature.connect(this, core, morphed))\n this.#connectFeatureNow(BINDINGS, \"bindings\", bindingsNeeded, (core) => bindingsFeature.connect(this, core, morphed))\n this.#connectFeatureNow(COMPUTE, \"compute\", computeNeeded, (core) => computeFeature.connect(this, core, morphed))\n this.#connectFeatureNow(SORTABLE, \"sortable\", sortableNeeded, (core) => sortableFeature.connect(this, core))\n this.#connectFeatureNow(DRAG, \"drag\", dragNeeded, (core) => dragFeature.connect(this, core))\n this.#connectFeatureNow(PRESENCE, \"presence\", presenceNeeded, (core) => presenceFeature.connect(this, core))\n this.#connectFeatureNow(COUNTDOWN, \"countdown\", countdownNeeded, (core) => countdownFeature.connect(this, core))\n }\n }\n\n // Once per connection: a marker check that throws loses that feature (and\n // is read again after a morph), a connect that throws loses it until the\n // root reconnects — never the rest of connect().\n #connectFeatureNow(bit, name, needs, connect) {\n if (!__SPLIT__) {\n if (this.#featuresAsked & bit) return\n try {\n if (!needs(this.element)) return\n } catch (error) {\n return this.#featureFailed(name, \"detect\", error)\n }\n this.#featuresAsked |= bit\n try {\n connect(this.#featureCore())\n this.#featuresOn |= bit\n } catch (error) {\n this.#featureFailed(name, \"connect\", error)\n }\n }\n }\n\n // FIRST in disconnect(), in connect order, while the root is still intact\n // (the draft flush reads its fields).\n #disconnectFeaturesNow() {\n if (!__SPLIT__) {\n const on = this.#featuresOn\n this.#featuresAsked = this.#featuresOn = 0\n const core = this.#featureCore()\n if (on & PERSIST) this.#disconnectFeatureNow(\"persist\", () => persistFeature.disconnect(this, core))\n if (on & DEFER) this.#disconnectFeatureNow(\"defer\", () => deferFeature.disconnect(this, core))\n if (on & FORM) this.#disconnectFeatureNow(\"form\", () => formFeature.disconnect(this, core))\n if (on & BINDINGS) this.#disconnectFeatureNow(\"bindings\", () => bindingsFeature.disconnect(this, core))\n if (on & COMPUTE) this.#disconnectFeatureNow(\"compute\", () => computeFeature.disconnect(this, core))\n if (on & SORTABLE) this.#disconnectFeatureNow(\"sortable\", () => sortableFeature.disconnect(this, core))\n if (on & DRAG) this.#disconnectFeatureNow(\"drag\", () => dragFeature.disconnect(this, core))\n if (on & PRESENCE) this.#disconnectFeatureNow(\"presence\", () => presenceFeature.disconnect(this, core))\n if (on & COUNTDOWN) this.#disconnectFeatureNow(\"countdown\", () => countdownFeature.disconnect(this, core))\n }\n }\n\n #disconnectFeatureNow(name, disconnect) {\n if (!__SPLIT__) {\n try {\n disconnect()\n } catch (error) {\n console.error(`[phlex-reactive] the \"${name}\" feature module failed to disconnect`, error)\n }\n }\n }\n\n // Starts the import of every feature this root needs and has not asked for\n // yet — at connect, and again after a morph of the root (which may have\n // added a marker; `morphed`). Every import starts now; each feature connects\n // when its own module has arrived (or is given up on) AND the features\n // before it in the table have had their turn — the order their connect-time\n // seeds depend on, never the order the network delivered them in — unless\n // this connection ended meanwhile.\n #loadFeatures(morphed) {\n if (__SPLIT__) {\n const epoch = this.#featureEpoch\n const names = []\n for (const [name, [needs]] of FEATURES) {\n if (!needs || this.#featuresWanted.has(name)) continue\n // Loaded, and with nothing to do per root: its marker need not be read.\n const loaded = featureModules.get(name)\n if (loaded && !loaded.connect) continue\n // A marker check that throws loses that feature, never the rest of\n // connect() — above all not the early drain that follows it.\n try {\n if (needs(this.element)) names.push(name)\n } catch (error) {\n this.#featureFailed(name, \"detect\", error)\n }\n }\n if (names.length === 0) return\n for (const name of names) this.#featuresWanted.add(name)\n // Every module already here (a later root, the next Turbo visit): connect\n // now, in this task — nothing to wait for, so nothing to gate or time out.\n if (!this.#featuresSettling && names.every((name) => featureModules.has(name))) {\n for (const name of names) this.#connectFeature(epoch, name, featureModules.get(name), morphed)\n return\n }\n for (const name of names) {\n const arrival = this.#awaitFeature(epoch, name)\n // Its turn comes after the features BEFORE it in the table that this\n // root is still waiting for — never after a later one, even one whose\n // import started earlier (a morph can add an earlier feature's marker).\n const earlier = []\n for (const key of FEATURES.keys()) {\n if (key === name) break\n if (this.#featureTurns.has(key)) earlier.push(this.#featureTurns.get(key))\n }\n const turn = Promise.all(earlier)\n .then(() => arrival)\n .then((feature) => feature && this.#connectFeature(epoch, name, feature, morphed))\n this.#featureTurns.set(name, turn)\n if (FEATURES.get(name)[3]) this.#featureGate = turn\n }\n // Requests stop waiting the moment the last gating feature has connected.\n const gate = this.#featureGate\n gate?.then(() => {\n if (this.#featureGate === gate) this.#featureGate = null\n })\n this.#featuresSettling = true\n const ready = Promise.all(this.#featureTurns.values()).then(() => {\n if (this.featuresReady === ready) this.#featuresSettling = false\n })\n this.featuresReady = ready\n }\n }\n\n // One feature's import, for one scan: resolves with the module, or with\n // null once the import failed or outlasted the timeout — so one slow or\n // broken feature never holds back the others. A module that arrives after\n // its timeout still connects (late, and so out of table order).\n #awaitFeature(epoch, name) {\n if (__SPLIT__) {\n const current = () => epoch === this.#featureEpoch\n this.#startWaitingFor(name)\n return new Promise((resolve) => {\n let timedOut = false\n const timer = setTimeout(() => {\n this.#featureWaits.delete(timer)\n timedOut = true\n if (current()) this.#featureFailed(name, \"timeout\", new Error(`not loaded after ${featureTimeoutMs()} ms`))\n resolve(null)\n }, featureTimeoutMs())\n // Kept with its resolve: a disconnect ends the wait (#disconnectFeatures),\n // or whatever is queued behind featuresReady would hang on a stuck import.\n this.#featureWaits.set(timer, resolve)\n const settled = () => {\n clearTimeout(timer)\n this.#featureWaits.delete(timer)\n }\n loadFeature(name).then(\n (feature) => {\n settled()\n // Late: connect one microtask on, after whatever else was queued on\n // this import while the root waited.\n if (timedOut) queueMicrotask(() => this.#connectFeature(epoch, name, feature))\n else resolve(feature)\n },\n (error) => {\n settled()\n if (current()) this.#featureFailed(name, \"load\", error)\n resolve(null)\n },\n )\n })\n }\n }\n\n // Run the feature's `waiting` hook (if it has one) and remember its undo and\n // what it records, for #connectFeature or #disconnectFeatures to hand on.\n #startWaitingFor(name) {\n if (__SPLIT__) {\n const hook = FEATURES.get(name)[2]\n if (!hook) return\n const pending = {}\n try {\n this.#featureHooks.set(name, [hook(this.element, pending), pending])\n FEATURE_SHARED.waiting.set(this.element, { ...FEATURE_SHARED.waiting.get(this.element), [name]: pending })\n } catch (error) {\n this.#featureFailed(name, \"detect\", error)\n }\n }\n }\n\n // The wait is over (the feature is here, or the root is leaving): undo the\n // hook and return what it recorded. The shared record goes with it unless\n // `abandoning`: a root that leaves keeps its record until abandon() has run,\n // so anything else queued on the same import still finds it.\n #stopWaitingFor(name, abandoning) {\n if (__SPLIT__) {\n const [undo, pending] = this.#featureHooks.get(name) ?? []\n this.#featureHooks.delete(name)\n if (!abandoning) forgetWaiting(this.element, name, pending)\n try {\n undo?.()\n } catch (error) {\n console.error(`[phlex-reactive] the \"${name}\" feature module failed to stop waiting`, error)\n }\n return pending\n }\n }\n\n #connectFeature(epoch, name, feature, morphed) {\n if (__SPLIT__) {\n if (epoch !== this.#featureEpoch) return\n const pending = this.#stopWaitingFor(name)\n try {\n feature.connect?.(this, this.#featureCore(), morphed, pending)\n this.#features.set(name, feature)\n } catch (error) {\n this.#featureFailed(name, \"connect\", error)\n }\n }\n }\n\n // The `core` handle a feature receives (see \"Feature modules\" above).\n #featureCore() {\n this.#featureHandle ??= {\n emit: (name, detail, options) => this.#emit(name, detail, options),\n reseed: () => this.#reseed(),\n // Resolves with the reply's HTML once the request has applied, or with\n // nothing when it failed or was vetoed. `busy` is on(busy:)'s hint.\n proceed: (target, action, params, busy) => this.#proceed(target, action, params, 0, 0, null, busy),\n forgetToken: () => {\n this.#tokenCache = undefined\n },\n owns: (el) => this.#ownsField(el),\n ownership: () => this.#ownershipFilter(),\n opTargets: (args) => this.#opTargets(args),\n diagnose: (label, args) => this.#diagnoseZeroTargets(label, args),\n // Apply an op list root-scoped, with the zero-target diagnostics; a\n // missing to: defaults to `defaultTo` (the reducer / completion-binding\n // convention is \"@root\").\n applyOps: (list, defaultTo) =>\n applyOps(\n list,\n (args) => this.#opTargets(defaultTo != null && args.to == null ? { ...args, to: defaultTo } : args),\n (name, args) => this.#diagnoseZeroTargets(`client op \"${name}\"`, args),\n ),\n listnavOptions: (event) => this.#listnavOptions(event),\n collectFields: () => this.#collectFields(),\n // A reactive_editable's value and its last server-rendered text (#351),\n // and whether the engine has contenteditable=\"plaintext-only\" (#355).\n plain: plaintextSupported,\n text: editableValue,\n served: serverText,\n // Copy one <img> onto another (issue #373): the reactive_media binding.\n copyMedia,\n // set_value's write (issue #374): reactive_show clear: (issue #394).\n setValue: setFieldValue,\n // The confirm gate, as dispatch() runs it: the resolver inside the chain\n // (a synchronous throw is a cancel), never a rejection out.\n confirm: (message, context) =>\n Promise.resolve()\n .then(() => confirmResolver(message, context))\n .catch(() => false),\n }\n return this.#featureHandle\n }\n\n // Re-run the connect-time seeds that read field values, in connect() order\n // — for a feature that changed those values after connect() ran (the draft\n // restore), or content a stream rendered into the root (issue #356). Each is\n // the same re-sync its feature runs after a morph; a root that did not opt\n // into one skips it. on-complete re-ARMS without firing. Form and bindings\n // also wire a binding whose gate reads true only now (a stream's content).\n #reseed() {\n if (!__SPLIT__) {\n if (this.#featuresOn & FORM) formFeature.reseed(this)\n if (this.#featuresOn & BINDINGS) bindingsFeature.reseed(this)\n if (this.#featuresOn & COMPUTE) computeFeature.seed(this)\n } else {\n this.#features.get(\"form\")?.reseed(this)\n this.#features.get(\"bindings\")?.reseed(this)\n this.#features.get(\"compute\")?.seed(this)\n }\n }\n\n // A feature that is missing leaves its part of the root dead: say so on\n // every root it costs (reactive:error, the error marker), and once in the\n // console. `phase` is \"detect\", \"load\", \"timeout\" or \"connect\".\n #featureFailed(name, phase, error) {\n logFeatureFailure(name, phase, error)\n this.#markError(\"feature\")\n // A lazy shell whose module will never come (a failed import stays failed\n // until the page is reloaded) must not go on shimmering as if it loaded.\n if (phase === \"load\" && this.element.getAttribute?.(\"data-reactive-defer-pending\")) {\n this.element.removeAttribute(\"data-reactive-defer-pending\")\n this.element.removeAttribute(\"aria-busy\")\n }\n this.#emit(\"reactive:error\", { kind: \"feature\", feature: name, phase, error })\n }\n\n // Runs FIRST in disconnect(), in table order, while the root is still\n // intact (the draft flush reads its fields). A feature whose disconnect\n // throws must not keep the others, or the rest of disconnect(), from running.\n #disconnectFeatures() {\n if (__SPLIT__) {\n this.#featureEpoch++\n // End every wait still open: nothing will connect on this connection, and\n // a request queued behind featuresReady must not hang on a stuck import.\n for (const [timer, resolve] of this.#featureWaits) {\n clearTimeout(timer)\n resolve(null)\n }\n this.#featureWaits.clear()\n // A feature this root was still waiting for never connected here: hand it\n // what its hook recorded, once (and if) the module arrives.\n for (const name of [...this.#featureHooks.keys()]) {\n const pending = this.#stopWaitingFor(name, true)\n const root = this.element\n withFeature(name, (feature) => {\n feature.abandon?.(root, pending)\n forgetWaiting(root, name, pending)\n })\n }\n this.#featuresWanted.clear()\n this.#featureTurns.clear()\n this.#featuresSettling = false\n this.#featureGate = null\n const connected = [...this.#features]\n this.#features.clear()\n for (const [name, feature] of connected) {\n try {\n feature.disconnect?.(this, this.#featureCore())\n } catch (error) {\n console.error(`[phlex-reactive] the \"${name}\" feature module failed to disconnect`, error)\n }\n }\n }\n }\n\n // Early triggers (issue #273): mark the root (the attribute is for CSS and\n // tests; early.js trusts the WeakSet joined at the top of connect()),\n // announce it with a bubbling reactive:connect, then replay its queued triggers.\n #announceConnected() {\n // The attribute is a connect-time marker only: an in-place morph writes\n // the server's attributes back and strips it (re-marking would cost every\n // root a morph listener — the \"a root that never opted in pays nothing\"\n // contract). The connected WeakSet is the truth; reactive:connect the signal.\n this.element.setAttribute?.(\"data-reactive-connected\", \"\")\n // Raw dispatch (as #emit), on the root only: connect() runs on an attached\n // element, so there is no detached-node fallback to make.\n if (this.element.isConnected) {\n this.element.dispatchEvent?.(\n new CustomEvent(\"reactive:connect\", { bubbles: true, composed: true, detail: { id: this.element.id } }),\n )\n }\n this.#drainEarly()\n // Issue #410: dispatches a client-only root inside handed over before this\n // root connected (a dormant root woken by that very trigger).\n // currentTarget is null once propagation ended: pin the trigger back on.\n for (const [event, target] of heldForwards.get(this.element) ?? []) {\n if (event.currentTarget !== target) Object.defineProperty(event, \"currentTarget\", { value: target, configurable: true })\n this.dispatch(event)\n }\n heldForwards.delete(this.element)\n }\n\n // The trigger elements whose :once descriptor THIS connection replayed —\n // forgotten on disconnect (see disconnect()).\n #earlySpentOn = new Set()\n\n // Takes this root's entries — and those of any root that left the page\n // before connecting (replaced by a stream), which no controller would ever\n // claim.\n #drainEarly() {\n const { queue } = earlyState()\n if (queue.length === 0) return\n const mine = []\n for (let i = queue.length - 1; i >= 0; i--) {\n const { root } = queue[i]\n if (root === this.element || !root.isConnected) mine.unshift(...queue.splice(i, 1))\n }\n const ttl = earlyTtlMs()\n // Two module instances of early.js (a bundled copy beside the pinned one)\n // each queue the same event: replay an (event, element) pair once. (One\n // event OBJECT dispatched twice on one element before connect also counts\n // once — indistinguishable here.) An element bound to both `click` and\n // `click@window` is heard by both listeners live, so its window-bound\n // entry (issue #303) is a separate pair.\n const replayed = new Map()\n for (const entry of mine) {\n const pair = replayed.get(entry.event) ?? [new Set(), new Set()]\n replayed.set(entry.event, pair)\n const seen = pair[+!!entry.win]\n if (seen.has(entry.el)) continue\n seen.add(entry.el)\n const limit = entry.win ? Math.min(WINDOW_EARLY_TTL_MS, ttl) : ttl\n const reason =\n entry.root !== this.element\n ? \"its root left the page before a controller connected\"\n : performance.now() - entry.at > limit\n ? `it is older than the ${limit} ms ${entry.win ? \"window-trigger\" : \"early-event\"} TTL`\n : entry.el.isConnected && this.element.contains(entry.el)\n ? null\n : \"its element left the root before the controller connected\"\n if (reason) {\n if (this.#verboseEnabled()) console.warn(`[phlex-reactive] dropped an early \"${entry.event.type}\" trigger: ${reason}`)\n continue\n }\n this.#replayEarly(entry)\n }\n }\n\n // One queued event, replayed once per matching descriptor — Stimulus calls\n // each binding with the SAME event object, so runOps' duplicate-binding\n // guard behaves as it does live. Each descriptor is re-checked here, where\n // Stimulus would check it: it must still be on the element (a morph may\n // have removed it), its key filter must match under the app's own key\n // mappings (early.js knows only Stimulus's defaults), and a :once one runs a single time however often it\n // was queued (spendEarlyOnce).\n #replayEarly({ event, el, descs, win }) {\n const replay = earlyReplayEvent(event, el, win)\n const keyMappings = this.application?.schema?.keyMappings\n const tokens = (el.getAttribute(\"data-action\") ?? \"\").split(/\\s+/)\n // A window-bound entry belongs to this root, not to `el` (issue #303).\n const owner = win ? this.element : el\n for (const desc of descs) {\n if (!tokens.includes(desc.token)) continue\n if (desc.filter && !keyFilterMatches(desc.filter, event, keyMappings)) continue\n // (`:once` is read off the token: early.js keeps its records minimal.)\n if (/#\\w+.*:once\\b/.test(desc.token)) {\n if (!spendEarlyOnce(owner, desc, win && JSON.stringify(replay.params))) continue\n this.#earlySpentOn.add(owner)\n }\n // The original may still be propagating (issue #274): its live arrival at\n // `el` — or at the window, for a window-bound entry — must not run this\n // binding again (earlyOnceSwallows).\n markEarlyReplay(event, owner, win ? `@${desc.method}` : desc.method)\n if (desc.method === \"runOps\") this.runOps(replay)\n else this.dispatch(replay)\n }\n }\n\n // Tear down any pending debounce timers when the controller leaves the DOM\n // (Turbo morph/navigation removes the element). Otherwise a timer that hasn't\n // fired yet would later call #enqueue on a disconnected controller — a round\n // trip against a detached element / stale token (issue #17 follow-up).\n // Throttle suppression timers (issue #80) are torn down the same way — a\n // leading-edge timer holds no pending POST, but leaving it running would leak\n // it past the element's life.\n disconnect() {\n // Features FIRST (issue #275): the persist feature flushes a pending draft\n // write while the fields are still readable (Turbo disconnects before\n // leaving the page — a fast visit otherwise loses the last keystrokes).\n if (__SPLIT__) this.#disconnectFeatures()\n else this.#disconnectFeaturesNow()\n this.#clearAllDebounces()\n this.#clearAllThrottles()\n if (this.#boundRootMorph) {\n this.element.removeEventListener?.(\"turbo:morph-element\", this.#boundRootMorph)\n this.element.removeEventListener?.(\"focusin\", this.#boundRootMorph)\n this.element.removeEventListener?.(STREAMED, this.#boundRootMorph)\n }\n // Early triggers (issue #273): a disconnected root records again.\n earlyState().connected.delete(this.element)\n if (rootControllers.get(this.element) === this) rootControllers.delete(this.element)\n this.element.removeAttribute?.(\"data-reactive-connected\")\n // A spent :once replay is remembered per element so a morph REPLY (root\n // still connected) cannot re-arm it. A disconnect drops Stimulus's own\n // `once` listeners and the next connect binds fresh ones, so the memory\n // goes too (issue #274: a dormant morph-back disconnects a root in place —\n // its :once trigger must work again after the re-wake).\n for (const el of this.#earlySpentOn) spentEarlyOnce.delete(el)\n this.#earlySpentOn.clear()\n }\n\n // Serialize requests per component. Each round trip rewrites the signed\n // token in the DOM (state lives in the token, not the client). If events\n // fire faster than round trips complete, concurrent requests would all read\n // the SAME stale token and clobber each other (last-write-wins). Chaining on\n // a per-controller promise makes each dispatch wait for the previous one, so\n // it always uses the freshest token.\n dispatch(event) {\n // Issue #410: a client-only root has no action of its own — a server\n // trigger inside it belongs to the token-bearing root above. FIRST, so that\n // root runs every check below with its own element.\n if (event.params?.action && event.params.action !== LAZY_MATERIALIZE_ACTION && this.#clientOnly()) {\n return this.#forwardToServerRoot(event)\n }\n // A shortcut (issue #357) that is the other platform's twin, or pressed\n // in a field the user is typing in, is a complete no-op: the native\n // default (text undo) proceeds and nothing is announced. Checked FIRST: a\n // delete/backspace shortcut (issue #366) binds a bare window keydown, so\n // every key the user presses lands here.\n const shortcut = event.params?.shortcut\n if (shortcut != null && !shortcutApplies(shortcut, event, this.application?.schema?.keyMappings)) return\n // A :once trigger already replayed on connect (issue #273) is spent.\n if (earlyOnceSwallows(event, \"dispatch\", this.application?.schema?.keyMappings, this.element)) return\n // `window` (renamed: never shadow the global) and `outside` are the event-\n // modifier params (issue #80). The client decides preventDefault behavior\n // from event.params — set by the Ruby on() — never by sniffing the\n // Stimulus descriptor.\n const { action, params, debounce, throttle, confirm, confirmWhen, outside, window: windowBound, optimistic } =\n event.params\n if (!action) return\n\n // The pending-state hint (issue #181): data-reactive-busy-param. During a\n // deploy overlap a page rendered by the PREVIOUS gem still emits the old\n // data-reactive-loading-param — read it as a fallback so an in-flight page\n // keeps its pending affordance until the next full render (the hints\n // feature remaps its `class:` key).\n const busy = event.params.busy ?? event.params.loading\n\n // Outside guard FIRST (issue #80): an outside: trigger only fires for\n // events whose target is OUTSIDE this component's ROOT (containment against\n // this.element — .contains includes the root itself). An event inside the\n // root must be a COMPLETE no-op — before preventDefault (the page's native\n // click behavior is untouched) and before the reactive:before-dispatch\n // lifecycle event (nothing to announce, nothing to veto).\n if (outside && this.element.contains(event.target)) return\n\n // The trigger is event.currentTarget — the element on(...) was spread onto —\n // NOT event.target (issue #99). A `<button><span>Save</span></button>` click\n // has target === the span, which carries no params and is the wrong element\n // to disable / swap text on. currentTarget is the bound element; fall back to\n // target for a directly-invoked/synthetic event. Captured now because\n // #proceed runs in a later microtask (after the confirm resolver), by which\n // point currentTarget is reset to null.\n const target = event.currentTarget ?? event.target\n\n // Stop native behavior (button submit / FORM NAVIGATION) HERE, synchronously\n // within the event dispatch — BEFORE the (possibly async) confirm gate below.\n // preventDefault() only works while the event is still being handled; once we\n // await the confirm resolver it's too late, and a `submit` trigger would\n // natively POST the form and navigate before the reactive round trip runs\n // (issue #11). For a `click` trigger there's no default to miss. This holds\n // for debounced triggers too — the round trip is deferred, but the native\n // default must still be prevented now. (Moved ahead of the confirm branch in\n // issue #55: an async resolver means we can't preventDefault after awaiting.)\n //\n // ONLY for element-bound triggers: a window-bound trigger (window:/outside:,\n // issue #80) hears EVERY matching event on the page — preventDefault-ing\n // those would kill every link click while a dropdown is mounted. The page's\n // native behavior proceeds alongside the reactive round trip.\n //\n // The `checked: :keep` optimistic hint (issue #98) OPTS OUT: for a click-bound\n // checkbox/radio the unconditional preventDefault is exactly what stops the\n // native flip from happening before the morph — so a bare checkbox click\n // (which has no form-navigation default to lose) skips it and flips now, and\n // the failure revert snaps it back. A `change`-bound trigger is unaffected —\n // `change` isn't cancelable, so preventDefault was already a no-op there.\n //\n // A shortcut (issue #357) IS prevented: its key filter has already narrowed\n // the window binding to one chord the page claims (mod+s must not also open\n // the browser's save dialog).\n if ((shortcut != null || !windowBound) && !this.#keepsNativeToggle(optimistic, target)) event.preventDefault()\n\n // A reactive_lazy(on:) shell's trigger (issue #276) goes through the one\n // materialize entry point, which dedupes it against the observer, the\n // re-armed listener and a morph-back. That entry point lives in the defer\n // feature (issue #275): the synchronous part of this dispatch is done, and\n // the load itself waits for the feature while it is still on its way.\n if (action === LAZY_MATERIALIZE_ACTION) {\n // Without the feature (it failed to load) a shell can still load the\n // plain way: the signed __materialize POST. A `cache:` shell carries no\n // token (issue #306) and has no such way: the feature's reactive:error\n // already said so, and nothing is sent.\n const materialize = () => {\n // (A shell whose defer connect threw has nothing wired: the plain POST.)\n if (!__SPLIT__) {\n if (this.#featuresOn & DEFER) return deferFeature.materialize(this)\n } else {\n const defer = this.#features.get(\"defer\")\n if (defer) return defer.materialize(this)\n }\n if (this.tokenValue) return this.#proceed(target, action, \"{}\")\n }\n return this.#featuresSettling ? this.featuresReady.then(materialize) : materialize()\n }\n\n // Resolve the EFFECTIVE confirm message (issue #179): a plain string confirm:\n // is that string (static, #52); a Hash confirm: (confirmWhen) evaluates its\n // condition/predicate over the collected fields and returns the message ONLY\n // when it fires, else null → no dialog. No confirm at all → also null. The\n // conditional form is the bindings feature's: on the opt-in client its\n // answer may come a moment later (a promise), once the module is here.\n const message = this.#effectiveConfirmMessage(confirm, confirmWhen)\n const proceed = () => this.#proceed(target, action, params, debounce, throttle, optimistic, busy)\n\n // No message → proceed straight away (unchanged fast path).\n if (!message) return proceed()\n if (message instanceof Promise) {\n return message.then((resolved) => (resolved ? this.#confirmThen(resolved, target, proceed) : proceed()))\n }\n this.#confirmThen(message, target, proceed)\n }\n\n // The confirmation gate (issue #52, made overridable + async in #55). A\n // reactive trigger can't use Hotwire's data-turbo-confirm — this controller\n // preempts the event — so a `confirm:` message routes through\n // confirmResolver (default window.confirm; an app can override it to reuse\n // Turbo.config.forms.confirm). The resolver may be sync or async; call it\n // INSIDE the chain (via the leading .then) so even a SYNCHRONOUS override\n // throw rejects this promise instead of escaping dispatch — a throwing\n // dialog is treated as a cancel, like the user dismissing it. The .catch is\n // scoped to the resolver step (→ false = cancel), so a dismissed/erroring\n // dialog never surfaces as an unhandled rejection AND a genuine bug inside\n // #proceed is NOT silently swallowed. Enqueue ONLY on a truthy resolution —\n // nothing is enqueued, no timer scheduled, otherwise. The resolver's\n // optional 2nd arg (issue #222) carries the trigger element, so an override\n // has the same ctx shape here as on nestedRemove ({ el, … }).\n #confirmThen(message, target, proceed) {\n return Promise.resolve()\n .then(() => confirmResolver(message, { el: target }))\n .catch(() => false)\n .then((ok) => {\n if (ok) proceed()\n })\n }\n\n // The effective confirm message (issue #179): the static string; or, for a\n // conditional confirm, what the bindings feature says — now when the module\n // is here, else a promise of it (the opt-in client's import window); or\n // null when neither applies. A module that cannot load, or is slower than\n // the feature timeout, answers null: no dialog, the endpoint's\n // authorize/default-deny is the real gate.\n #effectiveConfirmMessage(confirm, confirmWhen) {\n if (confirm) return confirm\n if (!confirmWhen) return null\n if (!__SPLIT__) return bindingsFeature.confirmMessage(this, this.#featureCore(), confirmWhen)\n else {\n const loaded = featureModules.get(\"bindings\")\n if (loaded) return loaded.confirmMessage(this, this.#featureCore(), confirmWhen)\n return this.#awaitFeatureOrNull(\"bindings\").then(\n (bindings) => bindings && bindings.confirmMessage(this, this.#featureCore(), confirmWhen),\n )\n }\n }\n\n // A feature's module, or null once its import failed (logged) or outlasted\n // the feature timeout — for the two places a REQUEST waits on an import\n // without a root connection to wait with (a hint, a conditional confirm).\n #awaitFeatureOrNull(name) {\n if (__SPLIT__) {\n let timer\n return Promise.race([\n loadFeature(name).catch((error) => (logFeatureFailure(name, \"load\", error), null)),\n new Promise((resolve) => (timer = setTimeout(() => resolve(null), featureTimeoutMs()))),\n ]).finally(() => clearTimeout(timer))\n }\n }\n\n\n // A tokenless client-only root (issue #410). tokenValue is the Stimulus view\n // of data-reactive-token-value; the attribute check also rules out a shell.\n #clientOnly() {\n return !this.tokenValue && clientOnlyRoot(this.element)\n }\n\n // Hand a server trigger to the nearest token-bearing root above (issue #410).\n // A dormant one is woken (as early.js wakes a root on its own trigger) and\n // runs the dispatch when it connects. With no such root nothing is sent.\n #forwardToServerRoot(event) {\n const root = serverRoot(this.element.parentElement, ANY_ROOT)\n if (!root) {\n if (this.element.closest?.('[data-reactive-verbose=\"true\"], [data-reactive-debug=\"true\"]'))\n console.warn(`[phlex-reactive] \"${event.params.action}\": no token on this root or above, nothing sent`)\n return\n }\n const controller = rootControllers.get(root)\n if (controller) return controller.dispatch(event)\n // Held past this event's dispatch: cancel its native default NOW, by\n // dispatch()'s own rule, or a submit/link would act before the request.\n const { shortcut, window: windowBound, optimistic } = event.params\n const target = event.currentTarget ?? event.target\n if ((shortcut != null || !windowBound) && !this.#keepsNativeToggle(optimistic, target)) event.preventDefault()\n heldForwards.set(root, [...(heldForwards.get(root) ?? []), [event, target]])\n const data = root.dataset\n if (data.reactiveDormant) {\n data.controller = `${data.controller ?? \"\"} ${data.reactiveDormant}`.trim()\n delete data.reactiveDormant\n }\n }\n\n // CLIENT-ONLY trigger entry point (issue #95) — the zero-round-trip sibling\n // of dispatch(). Wired by on_client: applies the declared op chain\n // (data-reactive-ops-param, built by Phlex::Reactive::JS) locally. NO token,\n // NO params, NO fetch, ever. Ops are ephemeral UI: any server re-render of\n // the component resets whatever they toggled (by design — a signed action\n // owns state that must survive re-renders).\n runOps(event) {\n // A lone shortcut record (Stimulus typecasts it to an object) whose chord\n // did not fire bails before any bookkeeping: a delete/backspace shortcut\n // (issue #366) binds a bare window keydown that hears every key. Several\n // records are checked one by one below.\n const lone = event.params?.ops\n if (lone?.shortcut != null && !shortcutApplies(lone.shortcut, event, this.application?.schema?.keyMappings)) return\n if (earlyOnceSwallows(event, \"runOps\", this.application?.schema?.keyMappings, this.element)) return\n if (bindingsAlreadyRan(event, this)) return\n const params = event.params ?? {}\n // The trigger element on_client was spread onto (issue #222 ctx: { el }),\n // captured now — currentTarget resets before the confirm resolver's microtask.\n const trigger = event.currentTarget ?? event.target\n // Issue #271: run only the binding record(s) whose descriptor fired. A\n // legacy [[op, args]] attr reads its flags from the element-wide params.\n const records = parseBindingRecords(params.ops)\n // Stimulus already applied the firing descriptor's key filter (custom\n // schema keys included), so a lone candidate on type + window-boundness\n // runs without a second key check; several same-type candidates are told\n // apart by their key filters, read through the app's Stimulus schema.\n const candidates = records.filter((record) => bindingMatches(record, event, null))\n const matching =\n candidates.length <= 1\n ? candidates\n : candidates.filter((record) => bindingMatches(record, event, this.application?.schema?.keyMappings))\n if (matching.length === 0 && records.length > 0) {\n // Issue #271: a hand-edited attr or a descriptor the matcher doesn't\n // know. Verbose gate only (the devtools feature dedupes).\n if (this.#verboseEnabled()) {\n if (__SPLIT__) this.#devtools((devtools) => devtools.noBinding(this, event))\n else devtoolsFeature.noBinding(this, event)\n }\n return\n }\n for (const record of matching) {\n // An early replay never runs an outside: record (issue #303: an outside\n // click before connect had nothing to close).\n if (record.outside && event[EARLY_KEY]) continue\n // A detail miss (issue #346) is a complete no-op — checked before the\n // once record is spent, preventDefault and the confirm gate.\n if (!detailMatches(record, event)) continue\n // So is a shortcut that is not this platform's twin or fired in an\n // editable target (issue #357).\n if (record.shortcut != null && !shortcutApplies(record.shortcut, event, this.application?.schema?.keyMappings))\n continue\n if (onceBindingSpent(this, event, record)) continue\n // A legacy attr reads its flags element-wide — but never a mix-ed on()'s\n // debounce/throttle params (issue #393): those belong to the dispatch.\n this.#runBinding(\n record.legacy ? { ...params, ops: record.ops, debounce: undefined, throttle: undefined } : record,\n event,\n trigger,\n records.indexOf(record),\n )\n }\n }\n\n // One on_client binding (issue #95): its outside guard, preventDefault rule,\n // confirm gate, then its ops — every flag read from the binding itself.\n #runBinding(record, event, trigger, ordinal) {\n const { ops, confirm, confirmWhen, outside, window: windowBound, shortcut } = record\n // Outside guard FIRST — identical semantics to dispatch() (issue #80): an\n // outside: trigger is a COMPLETE no-op for events inside this root, before\n // preventDefault and before any op runs.\n if (outside && this.element.contains(event.target)) return\n\n // Element-bound triggers preventDefault (a bare button inside a <form>\n // must not submit it); window-bound triggers (window:/outside:) never do —\n // they hear every matching event on the page, and preventDefault-ing those\n // would kill native clicks site-wide (issue #80 rationale). Runs BEFORE the\n // (possibly async) confirm gate below — a native default can't wait for a\n // pending dialog (same ordering as dispatch()).\n // A shortcut (issue #357) is prevented: it is one chord the page claims.\n if (shortcut != null || !windowBound) event.preventDefault()\n\n // Resolve the effective confirm message — static string, or the conditional\n // Hash form (issue #179) evaluated over collected fields (a promise of it\n // on the opt-in client while the bindings module is on its way). Null →\n // no dialog. The gate is here (the user gesture), NOT in #applyOps: that\n // applier is shared with the server-pushed reactive:js stream action,\n // which must NEVER prompt. The same confirmResolver gate on(:action,\n // confirm:) uses (issues #52/#55/#178), so a themed dialog covers both.\n const message = this.#effectiveConfirmMessage(confirm, confirmWhen)\n const apply = this.#waitedOps(record, ordinal, trigger, () => this.#applyOps(this.#parseOps(ops)))\n if (!message) return apply()\n if (message instanceof Promise) {\n return message.then((resolved) => (resolved ? this.#confirmThen(resolved, trigger, apply) : apply()))\n }\n this.#confirmThen(message, trigger, apply)\n }\n\n // Issue #393: an on_client record's debounce:/throttle: — on()'s timers\n // (#debounce trailing, #throttle leading), keyed on the trigger + the\n // record's ordinal + JSON (two identical mix-ed records keep their own\n // timers), so disconnect clears them with the dispatch ones. The wait sits\n // after the outside guard, preventDefault and confirm, as on()'s does.\n #waitedOps(record, ordinal, trigger, run) {\n const debounce = Number(record.debounce) || 0\n const throttle = Number(record.throttle) || 0\n if (debounce <= 0 && throttle <= 0) return run\n const key = JSON.stringify([ordinal, record])\n if (debounce > 0) return () => this.#debounce(trigger, key, debounce, run)\n return () => this.#throttle(trigger, key, throttle, run)\n }\n\n // Client-side compute (issue #104) is the compute feature's (issue #275,\n // features/compute.js). This is the method its descriptor names\n // (input->reactive#recompute), so it stays. While the module is on its way\n // (the opt-in client) the entry records the edit, and the module runs one\n // recompute when it connects.\n recompute(event) {\n return __SPLIT__\n ? featureModules.get(\"compute\")?.recompute(this, this.#featureCore(), event)\n : computeFeature.recompute(this, this.#featureCore(), event)\n }\n\n // Tag-chip input (issue #203) and draft nested rows (issue #208) are the\n // bindings feature's (issue #275, features/bindings.js). These are the\n // methods their descriptors name, so they stay: each hands the event to the\n // module — now, or, with the opt-in client, once it has arrived. The event\n // is dead by then, so what the module reads of it (the trigger, the target)\n // is captured here; the native default is prevented now, as the module\n // would have. tagsAdd keeps its two composition guards (an Enter that\n // listnav already took, or is about to take) in the same tick.\n tagsAdd(event) {\n if (event?.defaultPrevented) return\n if (this.#listnavOptions(event).some((el) => el.hasAttribute?.(\"data-reactive-highlighted\"))) return\n return __SPLIT__\n ? this.#bindingsAction(\"tagsAdd\", event)\n : bindingsFeature.tagsAdd(this, this.#featureCore(), event)\n }\n\n tagsPick(event) {\n return __SPLIT__\n ? this.#bindingsAction(\"tagsPick\", event)\n : bindingsFeature.tagsPick(this, this.#featureCore(), event)\n }\n\n tagsRemove(event) {\n return __SPLIT__\n ? this.#bindingsAction(\"tagsRemove\", event)\n : bindingsFeature.tagsRemove(this, this.#featureCore(), event)\n }\n\n nestedAdd(event) {\n return __SPLIT__\n ? this.#bindingsAction(\"nestedAdd\", event)\n : bindingsFeature.nestedAdd(this, this.#featureCore(), event)\n }\n\n nestedRemove(event) {\n return __SPLIT__\n ? this.#bindingsAction(\"nestedRemove\", event)\n : bindingsFeature.nestedRemove(this, this.#featureCore(), event)\n }\n\n syncNestedJson(event) {\n return __SPLIT__\n ? featureModules.get(\"bindings\")?.syncNestedJson(this, this.#featureCore(), event)\n : bindingsFeature.syncNestedJson(this, this.#featureCore(), event)\n }\n\n #bindingsAction(name, event) {\n if (__SPLIT__) {\n const loaded = featureModules.get(\"bindings\")\n if (loaded) return loaded[name](this, this.#featureCore(), event)\n event?.preventDefault?.()\n const snapshot = { currentTarget: event?.currentTarget ?? null, target: event?.target ?? null, preventDefault() {} }\n return withFeature(\"bindings\", (bindings) => bindings[name](this, this.#featureCore(), snapshot))\n }\n }\n\n // Dirty tracking (issue #103) is the form feature's (issue #275,\n // features/form.js). This is the method its descriptor names\n // (input->reactive#trackDirty), so it stays: a full re-scan, by the module\n // when it is here. While it is still on its way nothing is lost — it scans\n // the whole root when it connects.\n trackDirty() {\n if (!__SPLIT__) formFeature.scan(this, this.#featureCore())\n else (this.#features.get(\"form\") ?? featureModules.get(\"form\"))?.scan(this, this.#featureCore())\n }\n\n // Client-side list navigation (combobox keyboard nav, issue #72). Wired by\n // on(:search, …, listnav: \"[role=option]\"), which appends keyboard filters to\n // the input's data-action (keydown.down/up/enter/esc->reactive#listnav*) and\n // sets data-reactive-listnav-option-param. Arrow keys move a highlight among\n // the options WITH NO ROUND TRIP; Enter picks the highlighted option by\n // CLICKING IT (so its own on(:select) reactive trigger fires — selection stays\n // a signed action); Escape clears. Ephemeral highlight state lives on the DOM\n // (data-reactive-highlighted), never shipped to the client as trusted state.\n //\n // Issue #271: a container spread with reactive_listnav(focus: true) carries\n // data-reactive-listnav-focus-param=\"true\" — ROVING-FOCUS mode for a\n // role=menu: the same moves shift real focus among the items instead of a\n // highlight, and Home/End (listnavFirst/listnavLast) jump to the edges.\n listnavNext(event) {\n this.#listnavMove(event, (current, length) => (current < 0 ? 0 : (current + 1) % length))\n }\n\n listnavPrev(event) {\n this.#listnavMove(event, (current, length) => (current < 0 ? length - 1 : (current - 1 + length) % length))\n }\n\n listnavFirst(event) {\n this.#listnavMove(event, () => 0)\n }\n\n listnavLast(event) {\n this.#listnavMove(event, (_current, length) => length - 1)\n }\n\n // Enter: activate the highlighted option (fires its reactive select). No-op if\n // nothing is highlighted, and in that case DON'T preventDefault — Enter falls\n // through (there's no selection to make).\n listnavPick(event) {\n const options = this.#listnavOptions(event)\n const current = options.findIndex((el) => el.hasAttribute(\"data-reactive-highlighted\"))\n if (current < 0) return\n event.preventDefault()\n options[current].click()\n }\n\n listnavClose(event) {\n for (const el of this.#listnavOptions(event)) el.removeAttribute(\"data-reactive-highlighted\")\n }\n\n // Move among THIS root's options: `pick(current, length)` returns the next\n // index (current is -1 when nothing is highlighted/focused). preventDefault\n // stops Arrow keys moving the caret in a search input, and the page scrolling\n // under a menu. Highlight mode writes data-reactive-highlighted; focus mode\n // (issue #271) focuses the chosen item, current being the item that is or\n // contains document.activeElement.\n #listnavMove(event, pick) {\n const options = this.#listnavOptions(event)\n if (!options.length) return\n event.preventDefault()\n\n const focusMode = this.#listnavFocusMode(event)\n const active = globalThis.document?.activeElement\n const current = focusMode\n ? options.findIndex((el) => el === active || (active != null && el.contains?.(active)))\n : options.findIndex((el) => el.hasAttribute(\"data-reactive-highlighted\"))\n const chosen = options[pick(current, options.length)]\n\n if (focusMode) {\n chosen.focus?.()\n } else {\n for (const el of options) el.removeAttribute(\"data-reactive-highlighted\")\n chosen.setAttribute(\"data-reactive-highlighted\", \"true\")\n }\n chosen.scrollIntoView?.({ block: \"nearest\" })\n }\n\n // Focus mode is read off the TRIGGER like the option selector (issue #271).\n #listnavFocusMode(event) {\n const trigger = event?.currentTarget ?? event?.target ?? this.element\n return trigger.getAttribute?.(\"data-reactive-listnav-focus-param\") === \"true\"\n }\n\n // The option elements this root owns (skips nested reactive roots, issue #15),\n // per the selector on data-reactive-listnav-option-param. The attr rides on the\n // TRIGGER element (the search input on(...) is spread onto), read from the\n // event; the options are still scoped to this controller's root. Empty when\n // unset. Falls back to the root for a directly-invoked call (unit tests). The\n // ownership predicate is hoisted ONCE per keypress (issue #117) — in the common\n // no-nested-root case it is a constant true, skipping a closest() walk per\n // option. Hidden options are excluded (issue #163): a reactive_filter (or any\n // `hidden` toggle) removes a row from the keyboard path too, so an Arrow can't\n // highlight — and Enter can't pick — an invisible option.\n #listnavOptions(event) {\n const trigger = event?.currentTarget ?? event?.target ?? this.element\n const selector =\n trigger.getAttribute?.(\"data-reactive-listnav-option-param\") ??\n this.element.getAttribute(\"data-reactive-listnav-option-param\")\n if (!selector) return []\n const owns = this.#ownershipFilter()\n return Array.from(this.element.querySelectorAll(selector)).filter((el) => !el.hidden && owns(el))\n }\n\n // Enqueue the action — debounced if a debounce window is set, else immediately.\n // Split out of dispatch so both the no-confirm fast path and the post-confirm\n // microtask share one place (issue #55). `target` is captured up front because\n // this can run in a later microtask, after event.target has been reset.\n #proceed(target, action, params, debounce, throttle, optimistic, busy) {\n // Lifecycle veto point (issue #79): one cancelable reactive:before-dispatch\n // per user gesture — post-preventDefault, post-confirm, PRE-debounce (and\n // PRE-throttle, the same timing). event.preventDefault() skips the\n // debounce/throttle AND the enqueue entirely (nothing is scheduled).\n // retry() re-enters the queue directly, so this does NOT refire on a retry.\n // detail.params are the trigger's explicit params; sibling fields are\n // collected later, at send time.\n const before = this.#emit(\"reactive:before-dispatch\", {\n action,\n params: this.#parseParams(params),\n element: this.element,\n }, { cancelable: true })\n if (before.defaultPrevented) return\n\n // Debounced trigger (e.g. on(:update, event: \"input\", debounce: 300)):\n // coalesce rapid events into ONE round trip after a quiet period, instead of\n // one POST per keystroke (issue #17). A blur flushes a pending dispatch.\n // The optimistic hint (issue #98) and the busy state (issue #181) ride to\n // the flush too, so they apply ONCE per enqueue — a debounced input must not\n // flap toggle_class per keystroke, and its element must NOT be disabled\n // during the quiet period (that would break typing). Both apply at ENQUEUE.\n const ms = Number(debounce) || 0\n if (ms > 0) return this.#debounceDispatch(target, ms, action, params, optimistic, busy)\n\n // Throttled trigger (e.g. on(:track, event: \"scroll\", window: true,\n // throttle: 250), issue #80): LEADING-EDGE rate limit — fire the first\n // event immediately, drop the rest until the window elapses. debounce and\n // throttle are mutually exclusive (the Ruby on() raises on both).\n const throttleMs = Number(throttle) || 0\n if (throttleMs > 0) return this.#throttleDispatch(target, throttleMs, action, params, optimistic, busy)\n\n return this.#enqueue(action, params, optimistic, target, busy)\n }\n\n // Apply the optimistic hint ONCE (recording its inverse) and chain the round\n // trip, threading that inverse onto THIS queued request so the serialized\n // per-controller queue reverts the RIGHT request's hint on failure (issue\n // #98). Applying here — the single flush/enqueue point every path funnels\n // through — is what makes a hint apply once per enqueue, not per raw dispatch.\n //\n // The busy state (issue #181) applies here too, for the same reason: enqueue\n // is the moment the request is committed to the queue, so the always-on busy\n // vocabulary (data-reactive-busy on the trigger + root, aria-busy via a pending\n // counter, busy_on scoping) and the busy hint (disable + class + text swap)\n // cover the WHOLE pending window — queue wait included — not just the fetch. It\n // returns a `settle` closure that #perform runs in its finally (success OR\n // failure), guarded so a morph-replaced trigger is never clobbered.\n #enqueue(action, params, optimistic, target, busy) {\n // The always-on busy state and the activity signal, now: they cover the\n // whole pending window, a wait for the hints module included. `pending`\n // is what this request's hint engine recorded — filled in by the apply\n // below, which may run a moment later than this call.\n const pending = { inverse: null, undo: [], resurrect: null }\n const settle = this.#applyBusy(action, target, pending)\n const wanted = Boolean(optimistic || busy)\n const apply = (hints) => {\n if (!wanted || !hints) return\n const core = this.#featureCore()\n pending.inverse = __SPLIT__\n ? hints.optimistic(this, core, optimistic, target)\n : hintsFeature.optimistic(this, core, optimistic, target)\n pending.undo.push(...(__SPLIT__ ? hints.busy(this, core, busy, target) : hintsFeature.busy(this, core, busy, target)))\n // Debug-only teaching aid (issue #181): if optimistic: { hide: true } is\n // used for instant-delete but the reply RE-RENDERS the element (bringing\n // it back), that hint was pointless — the developer likely wanted\n // reply.remove. Capture the hidden nodes now; the success path re-checks\n // the OBSERVED DOM after the morph (never inferred from the verb).\n if (this.#debugEnabled()) {\n pending.resurrect = __SPLIT__\n ? hints.resurrection(this, core, optimistic, target)\n : hintsFeature.resurrection(this, core, optimistic, target)\n }\n }\n // A hint applies ONCE per enqueue — the single flush point every path\n // funnels through — never per raw dispatch. With the module here (the\n // default client, always) that is now, synchronously.\n const loaded = __SPLIT__ ? featureModules.get(\"hints\") : true\n if (!wanted || loaded) apply(loaded)\n // A feature still loading may be about to change what this request reads\n // (issue #275: the draft restore writes the fields #perform collects). Only\n // such a feature — one that `gates` — holds a request back, until it has\n // connected: at most the feature timeout, and on a root that never waited\n // for one, not at all. A feature that does not gate (the defer module)\n // never delays a request, however slow its import. A request WITH a hint\n // on a page that has not loaded the hints module waits for that import\n // the same way (at most the feature timeout), then applies the hint and\n // goes out — once.\n const perform = () => this.#perform(action, params, pending, settle)\n const ready = () => (this.#featureGate ? this.#featureGate.then(perform) : perform())\n this.queue = (this.queue ?? Promise.resolve()).then(() =>\n wanted && !loaded ? this.#awaitHints().then(apply).then(ready) : ready(),\n )\n return this.queue\n }\n\n // The hints module, or null once its import failed or outlasted the feature\n // timeout (the request then goes out without its hint).\n #awaitHints() {\n return this.#awaitFeatureOrNull(\"hints\")\n }\n\n // Reset a per-element timer; only enqueue the round trip after `ms` of quiet.\n // Also flush immediately on blur so leaving the field never drops the last\n // edit (a long debounce shouldn't swallow a value the user tabbed away from).\n #debounceDispatch(target, ms, action, params, optimistic, busy) {\n this.#debounce(target, DISPATCH_WAIT_KEY, ms, () => this.#enqueue(action, params, optimistic, target, busy))\n }\n\n // The one trailing-edge timer on() and on_client (issue #393) share. Timers\n // are keyed on target + key: on()'s dispatch uses DISPATCH_WAIT_KEY, an\n // on_client record its ordinal + JSON (it starts with \"[\"), so an on() debounce and an\n // on_client debounce on one input never cancel each other. A window-bound\n // trigger gets no blur flush: a window blur is a tab switch, not an edit.\n #debounce(target, key, ms, run) {\n this.#clearDebounce(target, key)\n\n const flush = () => {\n this.#clearDebounce(target, key)\n run()\n }\n const timer = setTimeout(flush, ms)\n if (!isWindow(target)) target?.addEventListener?.(\"blur\", flush, { once: true })\n let pending = this.#debounceTimers.get(target)\n if (!pending) this.#debounceTimers.set(target, (pending = new Map()))\n pending.set(key, { timer, flush })\n }\n\n #clearDebounce(target, key) {\n const byKey = this.#debounceTimers.get(target)\n const pending = byKey?.get(key)\n if (!pending) return\n clearTimeout(pending.timer)\n target?.removeEventListener?.(\"blur\", pending.flush)\n byKey.delete(key)\n if (byKey.size === 0) this.#debounceTimers.delete(target)\n }\n\n // Clear every pending debounce timer (used on disconnect). Reuses\n // #clearDebounce so all timer/listener teardown stays in one place. Snapshot\n // the entries first — #clearDebounce mutates the maps as it goes.\n #clearAllDebounces() {\n for (const [target, byKey] of [...this.#debounceTimers]) {\n for (const key of [...byKey.keys()]) this.#clearDebounce(target, key)\n }\n }\n\n // Leading-edge throttle (issue #80), mirroring #debounceDispatch: the FIRST\n // event fires immediately; a suppression timer then drops further events\n // until the window elapses (no trailing fire — dropped, not queued). Timers\n // are keyed on action + target, NOT target alone: window-bound scroll/resize\n // events all share event.target === document, so two window-bound triggers\n // on one component would otherwise collide on one timer.\n #throttleDispatch(target, ms, action, params, optimistic, busy) {\n return this.#throttle(target, action, ms, () => this.#enqueue(action, params, optimistic, target, busy))\n }\n\n // The leading-edge window on() and on_client (issue #393) share; an\n // on_client record keys on its ordinal + JSON, which no action name can equal.\n #throttle(target, key, ms, run) {\n const timers = this.#throttleTimers.get(target) ?? new Map()\n if (timers.has(key)) return // inside the window — suppress\n\n const timer = setTimeout(() => {\n timers.delete(key)\n if (timers.size === 0) this.#throttleTimers.delete(target)\n }, ms)\n timers.set(key, timer)\n this.#throttleTimers.set(target, timers)\n return run() // leading edge: fire NOW\n }\n\n // Clear every throttle suppression timer (used on disconnect, alongside\n // #clearAllDebounces) so nothing outlives the element.\n #clearAllThrottles() {\n for (const timers of this.#throttleTimers.values()) {\n for (const timer of timers.values()) clearTimeout(timer)\n }\n this.#throttleTimers.clear()\n }\n\n // Raw-dispatch a lifecycle CustomEvent (issue #79). Deliberately NOT\n // Stimulus's this.dispatch() helper — that name is SHADOWED by this\n // controller's own dispatch(event) action method. Bubbling + composed so a\n // page-level listener (or `data-action=\"reactive:error->toast#show\"` on an\n // ancestor) hears it. After a plain (non-morph) replace this.element is a\n // DETACHED node — a bubbling event on it never reaches document listeners —\n // so fall back to dispatching on document itself.\n #emit(name, detail, { cancelable = false } = {}) {\n const event = new CustomEvent(name, { bubbles: true, composed: true, cancelable, detail })\n const root = this.element.isConnected ? this.element : document\n root.dispatchEvent(event)\n return event\n }\n\n // reactive:error detail: { action, params, kind, status?, body?, retry }.\n // `params` are the FULL params that were sent (collected fields + explicit\n // trigger params). retry() re-enters the request queue with the ORIGINAL raw\n // trigger params, so #perform re-reads the freshest token and RE-COLLECTS the\n // sibling fields — nothing stale is replayed. It does not refire\n // reactive:before-dispatch (one veto per user gesture), and it no-ops with a\n // warning once the root has left the DOM (retrying against a detached\n // element would post a stale token into nowhere).\n #emitError(action, rawParams, sentParams, extra) {\n const retry = () => {\n if (!this.element.isConnected) {\n console.warn(\"[phlex-reactive] retry() ignored — the reactive root left the DOM\")\n return\n }\n return this.#enqueue(action, rawParams)\n }\n this.#emit(\"reactive:error\", { action, params: sentParams, ...extra, retry })\n }\n\n // Mark the reactive root as errored (issue #100) with the failure kind, so an\n // app can style it purely in CSS ([data-reactive-error] { … }) with zero JS.\n // Guarded — a plain replace may have detached the root before the failure lands.\n #markError(kind) {\n if (this.element?.isConnected === false) return\n this.element?.setAttribute?.(\"data-reactive-error\", kind)\n }\n\n // Clear the failure marker on the next successful apply (issue #100).\n #clearError() {\n this.element?.removeAttribute?.(\"data-reactive-error\")\n }\n\n // Offline fallback (issue #100): a network failure reached no server, so there\n // is no body to render. Clone the content of a server-rendered\n // <template data-reactive-error-flash> into the flash region so the user still\n // sees SOMETHING. The template is app-authored (trusted) — this is a pure\n // deep clone, never client templating of untrusted data. No template, or no\n // flash container, is a silent no-op (a page without the opt-in is unchanged).\n #renderNetworkFallback() {\n const template = document.querySelector(\"[data-reactive-error-flash]\")\n if (!template?.content) return\n // The flash region is the host-app container Response#flash targets. Its id\n // defaults to \"flash\" (Phlex::Reactive.flash_target); an app that customized\n // it can point the fallback at the same node by putting the template's\n // data-reactive-error-flash value there — but the common case is #flash.\n const targetId = template.getAttribute(\"data-reactive-error-flash\") || \"flash\"\n const region = document.getElementById(targetId)\n if (!region) return\n region.appendChild(template.content.cloneNode(true))\n }\n\n // Latency simulator (issue #102): the delay itself is the dev feature's.\n // A request waits for the module only while a delay is stored (so the very\n // first delayed request is delayed too); with none stored this is two reads.\n #maybeSimulateLatency() {\n if (!__SPLIT__) return devtoolsFeature.delay()\n else {\n const devtools = featureModules.get(\"devtools\")\n if (devtools) return devtools.delay()\n if (latencyStored()) return loadFeature(\"devtools\").then((loaded) => loaded.delay(), () => {})\n }\n }\n\n // Client debug mode (issue #108) — the \"devtools-lite\" lens. On when the Ruby\n // reactive_attrs stamped data-reactive-debug=\"true\" (Phlex::Reactive.debug).\n // Read live (a single getAttribute) so the whole feature is inert for any app\n // that never opts in: OFF → this returns false and #perform builds no debug\n // object, parses no response, and logs nothing (the \"zero cost when off\"\n // invariant — one nil-check per dispatch). Guarded for a stub root with no\n // getAttribute (unit harnesses) so it degrades to off, never throwing.\n #debugEnabled() {\n return this.element?.getAttribute?.(\"data-reactive-debug\") === \"true\"\n }\n\n // A monotonic timestamp for the round-trip duration (ms). performance.now is\n // monotonic (immune to a wall-clock adjustment mid-request); Date.now is the\n // fallback for an exotic environment without it. ONLY called on the debug path,\n // so it costs nothing when debug is off.\n #debugNow() {\n return typeof performance !== \"undefined\" && typeof performance.now === \"function\"\n ? performance.now()\n : Date.now()\n }\n\n async #perform(action, params, pending, settle) {\n // Auto-collect named field values inside this component so a button-\n // triggered action still receives sibling inputs (Livewire-style), plus any\n // chosen file inputs in the SAME walk. Explicit params\n // (data-reactive-params-param) win over collected fields.\n const { fields, files } = this.#collectFields()\n // Parse the explicit trigger params ONCE — reused below for allParams and, on\n // the debug path, for the params-only name list (so the trace can show\n // `params: [...]` distinct from the collected `+ collected: [...]`). #parseParams\n // is pure (a fresh object from a JSON string, or the same object by reference\n // when already parsed); allParams spreads a COPY and nothing mutates parsedParams\n // downstream (JSON.stringify / #buildFormData read allParams, a new object).\n const parsedParams = this.#parseParams(params)\n const allParams = { ...fields, ...parsedParams }\n const token = this.#currentToken\n\n // File/multipart path (issue #34): if THIS root has a populated\n // <input type=\"file\">, the action can't be JSON (JSON.stringify drops the\n // File). Send FormData instead — token + act + scalar params as fields, each\n // chosen file appended. The morph/token machinery downstream is identical;\n // only the request ENCODING differs when files are present.\n const multipart = files.length > 0\n const body = multipart\n ? this.#buildFormData(token, action, allParams, files)\n : JSON.stringify({ token, act: action, params: allParams })\n\n // Client debug mode (issue #108): build the trace object ONLY when debug is on\n // (one nil-check otherwise — zero cost off). It carries NAMES only: the\n // explicit trigger param names and the collected sibling field names, split so\n // the group shows `params: [...] + collected: [...]`. status/streams/\n // tokenRefreshed are filled in at the branch that knows them; the group is\n // emitted once in the finally so EVERY exit (success or any failure) logs.\n const debug = this.#debugEnabled()\n ? {\n action,\n paramNames: Object.keys(parsedParams),\n fieldNames: Object.keys(fields),\n encoding: multipart ? \"multipart\" : \"json\",\n status: null,\n streams: [],\n tokenRefreshed: false,\n started: this.#debugNow(),\n }\n : null\n\n // aria-busy on the root is now driven by the loading pending counter\n // (#applyLoading, applied at ENQUEUE so it covers the queue wait too), not\n // set here. #settleLoading in the finally clears it — see #enqueue.\n\n // Latency simulator (issue #102): the busy window (aria-busy + loading state)\n // is already applied at enqueue, so awaiting the configured delay HERE — after\n // that window opened and before the fetch — is what makes it visible on\n // localhost. A null/absent/non-positive sessionStorage key is a no-op (zero\n // production surface), so this line vanishes for any app that never opts in.\n await this.#maybeSimulateLatency()\n\n try {\n // Offline gate (issue #101), authoritative at the NETWORK BOUNDARY (send\n // time), not at enqueue. A click can enqueue while online and reach here\n // after going offline (a debounced/queued request, a rapid transition) —\n // gating in #perform makes the kind consistently \"offline\" for that whole\n // condition instead of leaking through as a \"network\" fetch throw. The\n // fetch never fires, so the edit is not half-sent; the finally still runs\n // (settle clears loading), the optimistic hint reverts, and retry() (which\n // re-enters #perform) re-checks and sends once back online.\n if (navigator.onLine === false) {\n this.#revertOptimistic(pending.inverse)\n this.#markError(\"offline\")\n this.#emitError(action, params, allParams, { kind: \"offline\" })\n return\n }\n\n let response\n try {\n const headers = {\n Accept: \"text/vnd.turbo-stream.html\",\n \"X-CSRF-Token\": this.#csrfToken(),\n }\n // For JSON we declare the content type; for multipart we must NOT — the\n // browser sets `multipart/form-data; boundary=…` itself, and overriding it\n // would strip the boundary and corrupt the body server-side.\n if (!multipart) headers[\"Content-Type\"] = \"application/json\"\n // Send the pgbus SSE connection id (if subscribed) so the server can\n // exclude this connection from its own broadcast echo — the actor\n // already gets the action's HTTP response. Harmless without pgbus.\n const connectionId = this.#connectionId()\n if (connectionId) headers[\"X-Pgbus-Connection\"] = connectionId\n // A presence root's own connection (issue #358): the row its presence\n // writes land on, whatever source comes first on the page.\n const presence =\n presenceNeeded(this.element) &&\n document.getElementById(`reactive-presence-src-${this.element.id}`)?.getAttribute(\"connection-id\")\n if (presence) headers[\"X-Reactive-Presence\"] = presence\n // The fields a morph held over a changed server value (issue #370).\n const held = heldHeader(this.element)\n if (held) headers[\"X-Reactive-Held\"] = held\n\n // ONLY `fetch` itself is the network boundary — offline, DNS, a reset\n // connection. Everything below this inner try (reading the body,\n // extracting the token, handing streams to Turbo) runs AFTER the\n // server already processed the mutation, so none of it belongs in the\n // `kind: \"network\"` / retriable bucket (CodeRabbit review on #89): if\n // renderStreamMessage throws, retry()ing would re-POST an action the\n // server already completed — see the outer catch below. (NOT a\n // reactive:applied LISTENER throwing — per the DOM spec, dispatchEvent\n // never propagates a listener's exception back to its caller, so that\n // case can't reach this catch at all; verified in the JS test suite.)\n countReactiveRequest(\"action\")\n response = await fetch(this.#actionPath(), {\n method: \"POST\",\n headers,\n body,\n credentials: \"same-origin\",\n // Bound the request (issue #101): a server that never answers used to\n // wedge this.queue forever (the finally that clears aria-busy/loading\n // never ran). AbortSignal.timeout(ms) aborts the fetch after the\n // configured window; the abort surfaces in this catch as a\n // DOMException named \"TimeoutError\" (see the branch below).\n signal: AbortSignal.timeout(this.#timeoutMs()),\n })\n } catch (error) {\n console.error(\"[phlex-reactive] action error\", error)\n this.#revertOptimistic(pending.inverse)\n // AbortSignal.timeout() rejects with a DOMException named \"TimeoutError\"\n // (a manual AbortController.abort() would be \"AbortError\" — we don't use\n // one, but accept it too for robustness). A timeout is NOT \"offline\":\n // the request left and the server didn't answer in time; connectivity is\n // unknown. So do NOT clone the offline-fallback template or mark network\n // — just fire kind:\"timeout\" (retriable). The queue still advances\n // (#perform returns, never rejects), so the hung request un-wedges it.\n if (error?.name === \"TimeoutError\" || error?.name === \"AbortError\") {\n this.#markError(\"timeout\")\n this.#emitError(action, params, allParams, { kind: \"timeout\" })\n return\n }\n // No server reached — nothing to render (issue #100). Clone a\n // server-rendered <template data-reactive-error-flash> into the flash\n // region as an offline fallback. The template is rendered by the app\n // (trusted), so this is a pure clone — no client templating of data.\n this.#renderNetworkFallback()\n this.#markError(\"network\")\n this.#emitError(action, params, allParams, { kind: \"network\" })\n return\n }\n\n // Debug (issue #108): the server answered — record the status for the trace\n // now, so every response branch below (redirected/http/content-type/ok) logs\n // it. The transport-failure branches above return before here (they have no\n // status); their group still fires from the finally with status null → \"—\".\n if (debug) debug.status = response.status\n\n if (response.redirected) {\n console.error(\"[phlex-reactive] action was redirected (auth/CSRF?) — no update applied\")\n this.#revertOptimistic(pending.inverse)\n this.#markError(\"redirected\")\n this.#emitError(action, params, allParams, { kind: \"redirected\", status: response.status })\n return\n }\n if (!response.ok) {\n const errorBody = await response.text()\n console.error(`[phlex-reactive] action failed: HTTP ${response.status}`, errorBody)\n this.#revertOptimistic(pending.inverse)\n // Render a non-OK turbo-stream body so a server-rendered error flash\n // (an error_flash rescue, or a status: :unprocessable_entity validation\n // reply from a plain controller) is actually SHOWN — instead of being\n // read only for the console (issue #100). #extractToken is run as usual;\n // it NO-OPS when no stream re-renders our id, so a 400 InvalidToken body\n // never refreshes the held identity token (which is not a nonce and stays\n // retry-valid — do not \"fix\" that). A non-turbo-stream body is left to the\n // console.error above (an HTML error page must not be handed to Turbo).\n if ((response.headers.get(\"Content-Type\") || \"\").includes(\"turbo-stream\")) {\n const fresh = this.#extractToken(errorBody)\n this.#currentToken = fresh ?? this.#currentToken\n if (debug) {\n if (__SPLIT__) this.#devtools((devtools) => devtools.recordBody(debug, errorBody, fresh))\n else devtoolsFeature.recordBody(debug, errorBody, fresh)\n }\n renderOwnReply(this.element, errorBody, true)\n }\n this.#markError(\"http\")\n this.#emitError(action, params, allParams, { kind: \"http\", status: response.status, body: errorBody })\n return\n }\n\n const contentType = response.headers.get(\"Content-Type\") || \"\"\n if (!contentType.includes(\"turbo-stream\")) {\n console.error(`[phlex-reactive] expected a turbo-stream, got \"${contentType}\" — no update applied`)\n this.#revertOptimistic(pending.inverse)\n this.#markError(\"content-type\")\n this.#emitError(action, params, allParams, { kind: \"content-type\", status: response.status })\n return\n }\n\n const html = await response.text()\n // Capture the new token from the response synchronously, so the next\n // queued request uses it without waiting for the async DOM morph.\n const fresh = this.#extractToken(html)\n this.#currentToken = fresh ?? this.#currentToken\n // Debug (issue #108): record the stream actions/targets + whether a refresh\n // arrived, from the body we JUST read (reuse — no second text() read). Never\n // the token or template contents.\n if (debug) {\n if (__SPLIT__) this.#devtools((devtools) => devtools.recordBody(debug, html, fresh))\n else devtoolsFeature.recordBody(debug, html, fresh)\n }\n // Turbo applies the <turbo-stream> ops by id. A plain replace is an\n // outerHTML swap (focus on the replaced subtree is lost); a method=\"morph\"\n // replace (Response.morph) or an update morphs in place, preserving the\n // focused input + caret on unchanged nodes — see issue #28. As the root's\n // own reply, it clears what the root held (issue #370), but what it\n // refused (issue #413).\n renderOwnReply(this.element, html, refusedHeader(response))\n // Debug-only (issue #181): the morph may apply a microtask later, so check\n // the resurrected-hide case AFTER it lands. Off the debug path, resurrect is\n // null — zero cost.\n if (pending.resurrect) queueMicrotask(pending.resurrect)\n // A successful apply CLEARS any prior failure marker (issue #100), so\n // error-driven CSS on the root (a red border, a shake) resets on recovery.\n this.#clearError()\n // Lifecycle hook (issue #79): the streams were HANDED TO Turbo — a\n // renderStreamMessage applies asynchronously, so the DOM mutation may\n // complete a tick later. Apps needing post-morph timing listen to Turbo's\n // own events; this one is for \"the action round trip succeeded\".\n this.#emit(\"reactive:applied\", { action, params: allParams, html })\n // What core.proceed resolves with: a feature can tell a reply that\n // applied from a failure (every other exit returns nothing).\n return html\n } catch (error) {\n // The server already processed this action successfully (we're past the\n // fetch) — a throw here is a CLIENT-side apply failure (a malformed\n // response, a broken Turbo render — NOT a reactive:applied listener\n // throw, which dispatchEvent never propagates here), not a transport\n // failure. kind: \"apply\" carries NO retry() — retrying would re-POST an\n // action the server already completed.\n console.error(\"[phlex-reactive] action error\", error)\n this.#revertOptimistic(pending.inverse)\n this.#emit(\"reactive:error\", { action, params: allParams, kind: \"apply\" })\n } finally {\n // Settle the loading state (issue #99): decrement the pending counter,\n // drop the trigger's/root's busy tokens, restore disabled/text/class —\n // guarded so a morph-replaced trigger is never clobbered. Runs on EVERY\n // exit (success, every failure branch, or an apply throw).\n settle?.()\n // Debug (issue #108): emit the group here so EVERY exit path logs exactly\n // once — success, any transport/response failure, or an apply throw. Null\n // when debug is off (zero cost). The round-trip ms is measured now, at the\n // finally, so it spans the whole #perform (fetch + apply) regardless of exit.\n if (debug) {\n if (__SPLIT__) this.#devtools((devtools) => devtools.trace(this, { ...debug, ms: this.#debugNow() - debug.started }))\n else devtoolsFeature.trace(this, { ...debug, ms: this.#debugNow() - debug.started })\n }\n }\n }\n\n get #currentToken() {\n return this.#tokenCache ?? this.tokenValue\n }\n\n set #currentToken(value) {\n this.#tokenCache = value\n }\n\n // A token written into the DOM wins over the cached one (issue #301): an\n // outside morph (a broadcast, a page refresh, a dormant morph-back) rewrites\n // the attribute while this controller survives, and Stimulus calls this on\n // that change and on a reconnect after it. Our own reply's morph writes the\n // token #extractToken already cached, so dropping the cache changes nothing —\n // unless a later reply was already extracted: then the attribute holds the\n // older token until that reply's morph lands, the same frame (Turbo renders\n // streams after nextRepaint), before any queued dispatch reads it.\n tokenValueChanged() {\n this.#tokenCache = undefined\n }\n\n // Read the next token for THIS controller — the one that re-renders THIS\n // element's id, never just the first token in the body (issue #46). On a\n // collection of REACTIVE rows the prepended/appended ROW carries its OWN\n // data-reactive-token-value and it sorts FIRST in the response; the list's own\n // fresh token rides a trailing `reactive:token` stream targeting the container.\n // Grabbing the first match stored the ROW's token, so the list's SECOND\n // dispatch sent a row token → failed verification → add-once-only. This mirrors\n // the server's carries_token_for? (#44): a stream carries OUR token only when it\n // RE-RENDERS our id (reactive:token / replace / update of `this.element.id`) —\n // append/prepend insert children and never count. Returns undefined when no\n // stream re-renders our id, so #currentToken keeps its existing value.\n #extractToken(html) {\n const id = this.element.id\n if (!id) {\n // No id to self-match (shouldn't happen for a reactive root). Fall back to\n // the legacy first-token behavior so a single-component response still works.\n return html.match(/data-reactive-token-value=\"([^\"]+)\"/)?.[1]\n }\n\n const { token, self } = this.#tokenRegexes(id)\n\n // The dedicated token-only refresh for THIS element (partial updates / the\n // collection container) — an attribute on the <turbo-stream> itself.\n const tokenStream = html.match(token)\n if (tokenStream) return tokenStream[1]\n\n // A full self re-render: a replace/update of THIS element whose template root\n // carries the fresh token. Scope the token search to that one stream so a\n // sibling/child token elsewhere in the body can't leak in.\n const selfStream = html.match(self)\n if (selfStream) return selfStream[1].match(/data-reactive-token-value=\"([^\"]+)\"/)?.[1]\n\n // Nothing re-rendered our id — keep the current token.\n return undefined\n }\n\n // The two per-id RegExps #extractToken uses to self-match this element's next\n // token (issue #118). `this.element.id` is page-stable, so these are compiled\n // ONCE and reused across every response — instead of allocating two fresh\n // RegExps per round trip. The memo is KEYED ON THE ID and rebuilt when it\n // changes: a re-render that re-identifies the root must scan for the NEW target,\n // never the stale one (or the token would freeze — see the id-change bun test).\n // The PATTERNS are byte-identical to the pre-memo inline literals; only their\n // allocation moved.\n #tokenRegexes(id) {\n const cache = this.#tokenRegexCache\n if (cache && cache.id === id) return cache\n const escaped = escapeRegExp(id)\n return (this.#tokenRegexCache = {\n id,\n token: new RegExp(\n `<turbo-stream\\\\b[^>]*\\\\baction=\"reactive:token\"[^>]*\\\\btarget=\"${escaped}\"[^>]*\\\\bdata-reactive-token-value=\"([^\"]+)\"`,\n ),\n self: new RegExp(\n `<turbo-stream\\\\b[^>]*\\\\baction=\"(?:replace|update)\"[^>]*\\\\btarget=\"${escaped}\"[^>]*>([\\\\s\\\\S]*?)</turbo-stream>`,\n ),\n })\n }\n\n // True when `el` is collected by THIS reactive root and not by a nested one.\n // A reactive component can be rendered inside another (both are\n // data-controller=\"reactive\" roots). querySelectorAll() descends into nested\n // roots, so without this guard an outer action would sweep the inner roots'\n // inputs into its own params (issue #15). An element belongs to this root iff\n // its nearest [data-controller~=\"reactive\"] ancestor is this.element.\n #ownsField(el) {\n return el.closest('[data-controller~=\"reactive\"]') === this.element\n }\n\n // A per-op ownership PREDICATE — the issue #117 fast path over issue #15\n // scoping. #ownsField answers \"is this element mine, not a nested reactive\n // root's\" with a per-element closest() walk; on a wide form or every keystroke\n // that walk runs per matched field. This hoists the DECISION to once per op.\n //\n // HYBRID GATE — closest() stays the source of truth; the nested-root query only\n // decides whether the fast path is SAFE:\n // * Fast path (overwhelmingly common): this root contains NO nested reactive\n // roots, so every element the caller's querySelectorAll returned is already\n // a direct descendant of this.element with no intervening reactive root —\n // it is ours. Return a constant-true predicate and skip the per-field\n // closest() walk entirely. That is the whole win.\n // * Nested case: fall back to the UNCHANGED #ownsField closest() check, so\n // scoping is byte-identical to before. We deliberately do NOT use\n // contains() here — the closest() form needs no node to implement\n // contains(), and on a real DOM the two agree for a descendant of\n // this.element (el.closest('[data-controller~=\"reactive\"]') === this.element\n // iff no nested reactive-root descendant contains el).\n //\n // Computed ONCE per dispatch-scoped op (per #collectFields call, per recompute,\n // per #listnavOptions) and NEVER stored on the instance — a morph replaces\n // nodes, so a cached predicate would close over stale roots.\n #ownershipFilter() {\n const nested = this.element.querySelectorAll('[data-controller~=\"reactive\"]')\n if (nested.length === 0) return () => true\n return (el) => this.#ownsField(el)\n }\n\n // #ownershipFilter for SERVER params (issue #410): nested client-only roots\n // are transparent, so a control under one is this root's when the first\n // token-bearing root above it is this.element. A nested token-bearing root\n // keeps its own fields (issue #15). Bindings, computes and listnav keep the\n // strict nearest-root #ownershipFilter.\n #serverFieldFilter() {\n const nested = this.element.querySelectorAll(AWAKE_ROOT)\n if (nested.length === 0 || [...nested].every(clientOnlyRoot)) return () => true\n return (el) => serverRoot(el, AWAKE_ROOT, this.element) === this.element\n }\n\n // One walk over THIS root's named controls (not a nested reactive root's),\n // returning both the scalar `fields` and any chosen `files`. The ownership\n // predicate is hoisted ONCE (issue #117) via #ownershipFilter — in the common\n // no-nested-root case it is a constant true, so we skip a closest() walk per\n // field. A file input's `.value` is the useless \"C:\\fakepath\\…\" string, never a\n // scalar — so its chosen files are collected separately (honoring `multiple`)\n // and it adds no phantom blank value (issue #34). An empty `files` keeps the\n // JSON path.\n #collectFields() {\n const fields = {}\n const files = []\n const owns = this.#serverFieldFilter() // compute ONCE per dispatch (issue #117)\n const controls = []\n this.element.querySelectorAll(\"input[name], select[name], textarea[name]\").forEach((field) => {\n if (!owns(field)) return\n if (field.type === \"file\") {\n // Carry the input's `multiple` flag so #buildFormData keeps the array\n // shape (params[name][]) even when the user picked exactly one file —\n // otherwise a [:file] schema would see a lone scalar upload and drop it.\n for (const file of field.files ?? []) files.push({ name: field.name, file, multiple: field.multiple })\n } else {\n // Held for a second pass: a hidden input is only a companion if a\n // checkbox somewhere in the root shares its name, which the first\n // occurrence cannot know yet.\n controls.push(field)\n }\n })\n // Collected before the first pass so the editor DOM is walked once.\n const editors = []\n this.element.querySelectorAll(`[name]${EDITOR_SELECTOR}`).forEach((el) => {\n if (owns(el)) editors.push(el) // the SAME hoisted predicate (nested reactive root, issue #15)\n })\n const arrayNames = this.#arrayFieldNames(controls)\n const companionNames = this.#companionNames(controls)\n for (const field of controls) {\n if (arrayNames.has(field.name)) {\n const slot = fields[field.name] ?? (fields[field.name] = [])\n if (field.type === \"checkbox\" || field.type === \"radio\") {\n // An unchecked box contributes NOTHING, the way a native submission\n // leaves it out. The group's value is the list of checked values, and\n // with none checked that list stays an EMPTY ARRAY rather than\n // vanishing: the action can tell \"the operator cleared them\" from\n // \"the group never rendered\", and an [:string] schema coerces [] to\n // []. A form body cannot carry the empty array, so #buildFormData\n // announces the group there instead.\n if (field.checked) slot.push(field.value)\n } else if (field.type === \"hidden\") {\n if (!companionNames.has(field.name)) slot.push(field.value)\n } else if (field.multiple && field.options) {\n for (const option of field.options) if (option.selected) slot.push(option.value)\n } else {\n slot.push(field.value)\n }\n } else if (field.type === \"checkbox\") {\n fields[field.name] = field.checked\n } else if (field.type === \"radio\") {\n if (field.checked) fields[field.name] = field.value\n } else {\n fields[field.name] = field.value\n }\n }\n // Named rich-text / custom editors (lexxy-editor, trix-editor) and bare\n // [contenteditable]. These aren't input/select/textarea, so the query above\n // skips them — without this, a reactive save posts an empty value and\n // silently wipes the field (issue #8). Read whatever the element exposes:\n // a custom editor's serialized `.value`, else its contenteditable text.\n // Under a plain name: only fill what the standard controls left absent or\n // empty, so a synced hidden input (e.g. Trix mirrors into one) still wins\n // when populated. Under a `[]` name the editor APPENDS to the group slot\n // instead, and a same-named hidden keeps its own say: nothing here can\n // tell a hidden that mirrors this editor from one that is a list JS\n // maintains.\n editors\n .forEach((el) => {\n // A plain element (e.g. a <div contenteditable>) has no `name` IDL\n // property — only the attribute — so read getAttribute, not el.name.\n const name = el.getAttribute(\"name\")\n if (!name) return\n const own = el.getAttribute(\"data-reactive-editable\") != null\n ? editableValue(el)\n : (el.value ?? el.textContent ?? el.innerHTML ?? \"\")\n // A `[]` name is a group here too: the editor APPENDS its value\n // instead of replacing the slot. Assigning a scalar posted\n // `{\"notes[]\": \"<p>x</p>\"}` while the draft snapshot pushed the same\n // control into an array — the wire and the draft disagreeing about one\n // field, and a declared array type seeing a string. A same-named hidden\n // is NOT read as this editor's twin: nothing here can tell a mirror\n // from a list JS maintains, and a value posted twice is visible while a\n // suppressed one is not.\n const existing = fields[name]\n // An editor that has not upgraded yet contributes NOTHING to a group.\n // Trix defines its elements in a setTimeout after load, and its \"\" is\n // not an empty value but an absent one: persistSnapshot omits such an\n // editor for the same reason, so this keeps the wire and the draft\n // saying the same thing.\n if (String(name).endsWith(\"[]\") && !collectorEditorReady(el)) return\n if (String(name).endsWith(\"[]\") && (existing === undefined || Array.isArray(existing))) {\n const slot = Array.isArray(existing) ? existing : (fields[name] = [])\n slot.push(own)\n return\n }\n // A scalar already under a `[]` name can only be a RADIO's: a radio\n // means \"pick one\" and keeps its single value with or without the\n // suffix, which is why #arrayFieldNames excepts it. Converting that to\n // a group here would discard the chosen value — measured, the post lost\n // it — so the editor stands down and the rule below applies, which\n // never overwrites a populated name.\n // Only fill what the standard controls left absent or empty, so a\n // synced hidden input still wins when populated.\n if (existing == null || existing === \"\") {\n fields[name] = own\n }\n })\n return { fields, files }\n }\n\n // Names collected as an ARRAY rather than a single value: a name carrying the\n // `[]` suffix, the HTML convention for a group. That suffix is the ONLY\n // trigger — a group says so, it is not inferred from two controls happening\n // to share a name. Radios are excluded BY DESIGN: a radio group shares one\n // name to mean \"pick one\", and it keeps posting the single checked value,\n // suffix or not.\n //\n // Issue #258: without this, `fields[name] = field.checked` wrote a boolean per\n // checkbox and same-named boxes overwrote each other, so three boxes with two\n // ticked left the browser as the LAST box's checked state — the chosen values\n // never reached the wire, whatever the action's schema declared.\n #arrayFieldNames(controls) {\n const names = new Set()\n for (const field of controls) {\n // A radio group shares one name BY DESIGN to mean \"pick one\" and keeps\n // its single checked value, `[]` suffix or not.\n if (field.type === \"radio\") continue\n if (String(field.name).endsWith(\"[]\")) names.add(field.name)\n }\n return names\n }\n\n // Names whose group carries a hidden COMPANION: a hidden input is Rails' way\n // of giving a checkbox a value for the unchecked case, and it is never a\n // chosen value. Measured from the helpers, the three shapes are:\n //\n // check_box(:u, :sub)\n // <input name=\"u[sub]\" type=\"hidden\" value=\"0\"><input type=\"checkbox\" value=\"1\" name=\"u[sub]\">\n // check_box(:u, :ids, {multiple: true}, \"3\")\n // <input name=\"u[ids][]\" type=\"hidden\" value=\"0\"><input type=\"checkbox\" value=\"3\" name=\"u[ids][]\">\n // collection_check_boxes(...) / an unchecked_value of nil\n // <input type=\"hidden\" name=\"u[ids][]\" value=\"\"> — or no hidden at all\n //\n // The value differs (unchecked_value, blank, absent), so the value cannot be\n // the test. What identifies a companion is that a checkbox shares its name.\n // A hidden WITHOUT a same-named checkbox is a list JS maintains, and its\n // value is a chosen value like any other.\n #companionNames(controls) {\n const names = new Set()\n for (const field of controls) if (field.type === \"checkbox\") names.add(field.name)\n return names\n }\n\n // Build the multipart body (issue #34). `token`/`act` are flat fields the\n // endpoint reads from params[:token]/params[:act]; scalar params nest under\n // params[<key>] (Rails parses the bracket into params[:params]); each file is\n // appended under params[<name>] (single) — a second file with the same name\n // (a `multiple` picker, several inputs sharing a name) is sent as\n // params[<name>][] so Rails coerces it to an array for a [:file] schema.\n #buildFormData(token, action, params, files) {\n const fd = new FormData()\n fd.append(\"token\", token)\n fd.append(\"act\", action)\n const emptyGroups = []\n for (const [key, value] of Object.entries(params)) {\n // A `[]` name carrying an array is the group shape (issue #258): every\n // element goes to params[name][], which Rack parses as an array. The\n // indexed form #appendField writes for a plain array (params[name][0],\n // params[name][1]) arrives as a hash of index keys — ParamSchema's array\n // type normalizes that back, but only an array type does, so the two\n // bodies would stop coercing identically for the same fields.\n //\n // An EMPTY group cannot be an empty array in a form body, so it is\n // ANNOUNCED instead: its key stays absent from params and its name goes\n // into `empty_groups[]`, a field of its own beside token/act/params. A\n // blank entry was the obvious alternative and is ambiguous — Rails leaves\n // `[\"\"]` to the caller, and a `[:date]` or `[:file]` element reads it as\n // \"did not come in\", so treating it as \"cleared\" would change what those\n // params mean. The field is additive: a server that ignores it behaves\n // exactly as it does today, and so does a client that never sends it.\n if (Array.isArray(value) && String(key).endsWith(\"[]\")) {\n if (value.length === 0) {\n emptyGroups.push(String(key).slice(0, -2))\n } else {\n const wire = `${this.#wireKey(key)}[]`\n for (const element of value) fd.append(wire, String(element))\n }\n } else {\n this.#appendField(fd, this.#wireKey(key), value)\n }\n }\n for (const name of emptyGroups) fd.append(\"empty_groups[]\", name)\n const multiNames = this.#multiFileNames(files)\n for (const { name, file, multiple } of files) {\n // params[name][] when the input is `multiple` (array shape even for one\n // file) OR the name repeats across inputs; otherwise a lone scalar file.\n const asArray = multiple || multiNames.has(name)\n const key = asArray ? `${this.#wireKey(name)}[]` : this.#wireKey(name)\n fd.append(key, file, file.name)\n }\n return fd\n }\n\n // The multipart wire key for a collected field/file name: params[...] with\n // the name's OWN brackets expanded into nesting segments (issue #231). The\n // old verbatim wrap of a Rails-bracketed name — params[blog_post[summary]] —\n // is unparseable by Rack: a scalar arrived as {\"blog_post[summary\" => {\"]\" =>\n // value}} (data corruption written through `update!` with a 200) and a file\n // never reached its schema key (silent drop). Expanding blog_post[summary]\n // into params[blog_post][summary] mirrors the server's bracket_path exactly —\n // split at the first \"[\", then each non-empty bracket segment; an empty\n // trailing segment (tags[]) drops, matching how the JSON path's\n // expand_bracket_keys coerces the same name — so a multipart body and a JSON\n // body coerce identically for the same fields (the documented contract).\n // A flat name stays a single params[name] wrap, byte-identical to before.\n #wireKey(name) {\n const raw = String(name)\n const head = raw.indexOf(\"[\")\n if (head === -1) return `params[${raw}]`\n const segments = [raw.slice(0, head), ...(raw.slice(head).match(/[^\\[\\]]+/g) ?? [])]\n return `params${segments.map((segment) => `[${segment}]`).join(\"\")}`\n }\n\n // Append a param leaf to FormData under its bracketed key. FormData carries\n // only strings, so a NON-scalar param (a nested object or an array) is\n // bracket-EXPANDED into params[key][sub] / params[key][index][...] fields —\n // the SAME Rails-form shape the server's expand_bracket_keys / array_values\n // already parse, so a JSON body and a multipart body coerce identically\n // (issue #39). Previously a non-scalar was JSON.stringify'd into one\n // params[key]='<json>' field, which the server received as an un-decodable\n // String leaf and DROPPED (nested hash -> {}, array -> key removed).\n //\n // Arrays use NUMERIC indices (params[key][0], params[key][1]) — required for\n // an array-of-hash so each element's sub-keys stay grouped and the server's\n // index-hash sort rebuilds order; params[key][] would collapse them. A scalar\n // (string/number/boolean) is one string field, mirroring the JSON wire shape\n // (the server's :boolean/:integer casts read \"true\"/\"42\"). null/undefined is\n // an empty field. An EMPTY array/object emits NOTHING — FormData can't carry\n // []/{}, so the key is omitted and the action's keyword default applies (this\n // differs from the JSON path, where an explicit [] coerces to an empty array).\n #appendField(fd, key, value) {\n if (value == null) {\n fd.append(key, \"\")\n } else if (Array.isArray(value)) {\n value.forEach((element, index) => this.#appendField(fd, `${key}[${index}]`, element))\n } else if (typeof value === \"object\") {\n for (const [subKey, subValue] of Object.entries(value)) {\n this.#appendField(fd, `${key}[${subKey}]`, subValue)\n }\n } else {\n fd.append(key, String(value))\n }\n }\n\n // Names that appear more than once across the chosen files (a `multiple`\n // picker, or several file inputs sharing a name) — those go to params[name][]\n // so the server sees an array; a lone file stays params[name].\n #multiFileNames(files) {\n const counts = new Map()\n for (const { name } of files) counts.set(name, (counts.get(name) ?? 0) + 1)\n return new Set([...counts].filter(([, n]) => n > 1).map(([name]) => name))\n }\n\n #parseParams(raw) {\n if (!raw) return {}\n try {\n return typeof raw === \"string\" ? JSON.parse(raw) : raw\n } catch {\n return {}\n }\n }\n\n // Stimulus typecasts a JSON param to the parsed array already; a raw string\n // (hand-built attr, non-typecasting harness) is parsed here. Delegates to the\n // shared parseOps so the controller and the reactive:js stream action (issue\n // #97) treat a malformed ops attr identically (→ [], never a throw).\n #parseOps(raw) {\n return parseOps(raw)\n }\n\n // Interpret a [[name, args], ...] op list against this root (issue #95),\n // scoping each op's targets to this controller's own root via #opTargets (the\n // nested-reactive-root ownership filter, issue #15). The whitelist + skip\n // logic lives in the shared applyOps so runOps and the reactive:js stream\n // action interpret the SAME vocabulary the SAME way (client-side default-deny).\n #applyOps(list) {\n applyOps(\n list,\n (args) => this.#opTargets(args),\n (name, args) => this.#diagnoseZeroTargets(`client op \"${name}\"`, args),\n )\n }\n\n // Issue #237: the verbose gate for zero-target diagnostics — the\n // verbose_errors stamp (ON by default in dev/test) or full debug mode (a\n // debug user must never see less). Read live off the root like #debugEnabled.\n #verboseEnabled() {\n return this.element?.getAttribute?.(\"data-reactive-verbose\") === \"true\" || this.#debugEnabled()\n }\n\n // Issue #237: called when a selector-form target resolved to ZERO elements on\n // this root. Past the verbose gate the devtools feature (issue #275) does the\n // work: the DOM probes, the dedupe, the trap-specific hint.\n #diagnoseZeroTargets(label, args) {\n if (!this.#verboseEnabled()) return\n if (__SPLIT__) this.#devtools((devtools) => devtools.diagnose(this, label, args))\n else devtoolsFeature.diagnose(this, label, args)\n }\n\n // Run `use` with the devtools feature: now when it is loaded (the default\n // client, always), else once it has arrived — a warning or a trace a tick\n // late is still a warning. Called only past a debug/verbose gate, so a page\n // with neither never asks for the module.\n #devtools(use) {\n if (__SPLIT__) withFeature(\"devtools\", use)\n }\n\n // Resolve an op's targets: \"@root\" is this element; a selector resolves\n // WITHIN this root and excludes nested reactive roots' subtrees (issue #15\n // semantics — the same nearest-root ownership check the field walk uses);\n // global: true opts a single op out to document-wide.\n #opTargets(args) {\n const to = args.to\n if (to === \"@root\") return [this.element]\n if (typeof to !== \"string\" || to === \"\") return []\n if (args.global) return [...document.querySelectorAll(to)]\n return [...this.element.querySelectorAll(to)].filter((el) => this.#ownsField(el))\n }\n\n // Whether a click-bound checkbox/radio trigger should keep its NATIVE flip\n // (issue #98). `checked: :keep` means \"let the control flip now\": the\n // unconditional preventDefault is exactly what suppresses that flip until the\n // morph, so we skip it — but ONLY for a checkbox/radio (a bare toggle click\n // has no form-navigation default to lose). Any other element (a button) keeps\n // preventDefault so an in-form submit can't navigate.\n #keepsNativeToggle(optimistic, target) {\n if (optimistic?.checked !== \"keep\") return false\n const type = target?.type\n return type === \"checkbox\" || type === \"radio\"\n }\n\n // Replay the recorded undo ops on failure (issue #98), guarded by isConnected:\n // a plain (non-morph) replace can detach this subtree before the failure lands,\n // and reverting a stale/detached node is pointless (it's gone) — so a\n // disconnected root skips the revert entirely. On success NOTHING calls this:\n // the server re-render overwrites the hint, or (reply.remove / streams-only)\n // the hint is deliberately left standing.\n #revertOptimistic(inverse) {\n if (!inverse) return\n if (!this.element.isConnected) return\n for (const undo of inverse) undo()\n }\n\n // Apply the BUSY state for THIS enqueue (issue #181) and return a `settle`\n // closure that undoes exactly this enqueue's contribution when the round trip\n // finishes (success OR any failure). Everything is refcounted so overlapping\n // enqueues never clobber: A's settle can't clear busy while B is still pending.\n //\n // Two layers:\n // 1. The ALWAYS-ON busy vocabulary (fires for every action, no hint needed):\n // data-reactive-busy=\"<action>\" on the trigger and the root (a\n // space-separated, per-action refcounted set), aria-busy on the root (a\n // pending counter), and data-reactive-busy on any busy_on element scoped\n // to this action. Apps style a spinner with pure CSS and zero Ruby.\n // 2. The busy HINT (only when busy: was declared): the SAME cosmetic op set\n // as optimistic: (class ops, hide/show, disable, text), applied by the\n // hints feature (features/hints.js) and reverted on SETTLE (not on\n // failure) — its undo ops land in `pending.undo`, which the settle\n // closure replays. They apply at ENQUEUE — never during a debounce quiet\n // period — so a debounced input is not disabled mid-typing.\n #applyBusy(action, trigger, pending) {\n this.#markBusy(action, trigger)\n // Global activity signal (issue #201): this enqueue is one in-flight reactive\n // operation. Entered HERE (at enqueue, via #applyBusy) so the document marker\n // covers the whole pending window — queue wait included — exactly like the\n // per-root busy vocabulary. Exited once in the settle closure below, which\n // #perform runs in its finally on EVERY exit path (success or any failure).\n enterReactiveActivity()\n\n let settled = false\n return () => {\n if (settled) return // one settle per enqueue, even if called twice\n settled = true\n this.#unmarkBusy(action, trigger)\n exitReactiveActivity()\n // Busy reverts on SETTLE regardless of outcome, guarded per element.\n for (const op of pending.undo) op()\n }\n }\n\n // Layer 1 — the always-on busy markers. Trigger + root carry the action token;\n // the root's counter drives aria-busy; busy_on elements scoped to this action\n // light up. Refcounts (#busyActions, #busyPending) so overlapping requests\n // don't clear each other.\n #markBusy(action, trigger) {\n this.#setBusyToken(trigger, action, +1)\n this.#setBusyToken(this.element, action, +1)\n\n this.#busyActions.set(action, (this.#busyActions.get(action) ?? 0) + 1)\n if (this.#busyPending++ === 0) this.element.setAttribute(\"aria-busy\", \"true\")\n\n for (const el of this.#busyOnTargets(action)) this.#setBusyToken(el, action, +1)\n }\n\n #unmarkBusy(action, trigger) {\n this.#setBusyToken(trigger, action, -1)\n this.#setBusyToken(this.element, action, -1)\n\n const count = (this.#busyActions.get(action) ?? 1) - 1\n if (count <= 0) this.#busyActions.delete(action)\n else this.#busyActions.set(action, count)\n\n if (--this.#busyPending <= 0) {\n this.#busyPending = 0\n this.element.removeAttribute(\"aria-busy\")\n }\n\n for (const el of this.#busyOnTargets(action)) this.#setBusyToken(el, action, -1)\n }\n\n // Add (+1) or remove (-1) `action` from an element's space-separated\n // data-reactive-busy token set, refcounted PER ELEMENT+ACTION so two queued\n // requests of the same action on the same element don't drop the token early\n // (and two DIFFERENT actions both keep their token — the set never clobbers).\n // The attribute is removed only when the set empties. No-op on a nullish/\n // detached element (a morph may have replaced the trigger before settle).\n #setBusyToken(el, action, delta) {\n if (!el || typeof el.getAttribute !== \"function\") return\n\n const counts = (this.#busyTokenCounts.get(el) ?? new Map())\n const next = (counts.get(action) ?? 0) + delta\n if (next <= 0) counts.delete(action)\n else counts.set(action, next)\n\n if (counts.size === 0) {\n this.#busyTokenCounts.delete(el)\n el.removeAttribute(\"data-reactive-busy\")\n return\n }\n this.#busyTokenCounts.set(el, counts)\n el.setAttribute(\"data-reactive-busy\", [...counts.keys()].join(\" \"))\n }\n\n // busy_on elements scoped to THIS action, owned by this root (not a nested\n // reactive root's, issue #15). data-reactive-busy-on=\"<action>\" is the marker\n // busy_on(:action) emits.\n #busyOnTargets(action) {\n const nodes = this.element.querySelectorAll?.(\"[data-reactive-busy-on]\") ?? []\n return [...nodes].filter(\n (el) => el.getAttribute(\"data-reactive-busy-on\") === action && this.#ownsField(el),\n )\n }\n\n // The action path comes from a <meta> tag that is fixed for the page's life,\n // so resolve it once per controller and cache it — avoids a querySelector on\n // every dispatch (this runs on the request hot path, once per click/keystroke\n // round trip). Cached on the instance, so a fresh connect() (after a Turbo\n // navigation swaps the element) re-reads it.\n #actionPath() {\n return (this.#actionPathCache ??=\n document.querySelector('meta[name=\"phlex-reactive-action-path\"]')?.content ||\n \"/reactive/actions\")\n }\n\n // The per-request timeout in ms (issue #101), from a page-stable\n // <meta name=\"phlex-reactive-timeout\"> (default 30000). Cached per-controller\n // like the action path. Parsed defensively: a missing/blank/non-positive/NaN\n // value falls back to the default, so a typo'd meta can never disable the\n // timeout (which would reintroduce the wedged-queue bug) or set a zero/negative\n // window that aborts instantly.\n #timeoutMs() {\n if (this.#timeoutMsCache != null) return this.#timeoutMsCache\n const raw = document.querySelector('meta[name=\"phlex-reactive-timeout\"]')?.content\n const ms = Number(raw)\n return (this.#timeoutMsCache = Number.isFinite(ms) && ms > 0 ? ms : 30000)\n }\n\n // CSRF token and connection id are read LIVE (not cached) on purpose: Rails\n // can rotate the CSRF token, and the pgbus connection id changes on an SSE\n // reconnect — caching either would send a stale value. A single querySelector\n // per request is cheap next to the round trip itself.\n #csrfToken() {\n return document.querySelector('meta[name=\"csrf-token\"]')?.content ?? \"\"\n }\n\n // The pgbus SSE connection id, if the page is subscribed to a stream. pgbus\n // reflects it onto the <pgbus-stream-source connection-id=\"…\"> element (and\n // apps may mirror it to <meta name=\"pgbus-connection-id\">). Returns null\n // when not present (e.g. no pgbus, or no active subscription) — the header\n // is then simply omitted.\n #connectionId() {\n return (\n document.querySelector(\"pgbus-stream-source[connection-id]\")?.getAttribute(\"connection-id\") ||\n document.querySelector('meta[name=\"pgbus-connection-id\"]')?.content ||\n null\n )\n }\n}\n\n// Register with Turbo as soon as it is there. LAST in the file: registration\n// reads the feature table (the dev module is loaded from it), which must\n// exist by then.\nif (typeof window !== \"undefined\") {\n if (window.Turbo) registerReactiveActions()\n else document.addEventListener(\"turbo:load\", registerReactiveActions, { once: true })\n}\n",
|
|
6
|
+
"// phlex/reactive/core — the OPT-IN client: the reactive controller without\n// its feature modules, which it imports when a root on the page needs one\n// (issue #275). The default is phlex/reactive/reactive_controller: one file\n// with every feature inside, nothing imported later. An app imports ONE of\n// the two, never both.\n//\n// The controller itself is the shared runtime (runtime.js); the shipped\n// core.min.js is this file BUNDLED with it. All this entry adds is where each\n// feature module lives: one LITERAL import() per feature, by the bare\n// specifier the engine pins (phlex/reactive/features/<name>) — literal so a\n// bundler can see it and split the feature into its own chunk, bare so the\n// import map resolves it to that module's own digested file (issue #57).\n//\n// What the opt-in costs a page is documented in the README (\"The split\n// client\"): the first root that needs a feature waits for its import.\nimport ReactiveController, {\n loadReactiveFeature,\n onReactiveStreamWithoutEffects,\n registerReactiveDev,\n registerReactiveFeatureLoader,\n unloadedFeaturesFor,\n} from \"phlex/reactive/runtime\"\n\nregisterReactiveFeatureLoader(\"persist\", () => import(\"phlex/reactive/features/persist\"), waitingForPersist)\nregisterReactiveFeatureLoader(\"defer\", () => import(\"phlex/reactive/features/defer\"))\nregisterReactiveFeatureLoader(\"form\", () => import(\"phlex/reactive/features/form\"))\nregisterReactiveFeatureLoader(\"bindings\", () => import(\"phlex/reactive/features/bindings\"))\nregisterReactiveFeatureLoader(\"compute\", () => import(\"phlex/reactive/features/compute\"), waitingForEdit)\nregisterReactiveFeatureLoader(\"sortable\", () => import(\"phlex/reactive/features/sortable\"))\nregisterReactiveFeatureLoader(\"drag\", () => import(\"phlex/reactive/features/drag\"))\nregisterReactiveFeatureLoader(\"presence\", () => import(\"phlex/reactive/features/presence\"))\nregisterReactiveFeatureLoader(\"countdown\", () => import(\"phlex/reactive/features/countdown\"))\nregisterReactiveFeatureLoader(\"effects\", () => import(\"phlex/reactive/features/effects\"))\nregisterReactiveFeatureLoader(\"hints\", () => import(\"phlex/reactive/features/hints\"))\nregisterReactiveFeatureLoader(\"devtools\", () => import(\"phlex/reactive/features/devtools\"))\n// The runtime registered with Turbo before this file ran: now it knows where\n// the dev module is, a development page can have it.\nregisterReactiveDev()\n\n// --- While the persist module is on its way ---------------------------------------\n// What the draft code must not miss while it is on its way: that the\n// user edited a field (the edit must be drafted once the module is here)\n// and that the form around the root was submitted successfully (the\n// draft must be forgotten, not restored). The feature listens for both\n// itself once it has connected.\nfunction waitingForPersist(root, pending) {\n const edited = () => {\n pending.edited = true\n }\n const submitted = (event) => {\n const form = event.target\n if (event.detail?.success && form?.tagName === \"FORM\" && form.contains?.(root)) pending.submitted = true\n }\n // (Lexical and Trix swallow the native `input` of their editor; their\n // own bubbling change events are the keystroke signal.)\n const edits = [\"input\", \"change\", \"lexxy:change\", \"trix-change\"]\n for (const type of edits) root.addEventListener?.(type, edited)\n document.addEventListener(\"turbo:submit-end\", submitted)\n return () => {\n for (const type of edits) root.removeEventListener?.(type, edited)\n document.removeEventListener(\"turbo:submit-end\", submitted)\n }\n}\n\n// --- While the compute module is on its way --------------------------------------\n// An edit the user made before the module could recompute: it runs one pass\n// when it connects, over the fields as the user left them.\nfunction waitingForEdit(root, pending) {\n const edited = () => {\n pending.edited = true\n }\n root.addEventListener?.(\"input\", edited)\n root.addEventListener?.(\"change\", edited)\n return () => {\n root.removeEventListener?.(\"input\", edited)\n root.removeEventListener?.(\"change\", edited)\n }\n}\n\n// --- A stream that needs a module before it is loaded ------------------------------\n// A stream can bring the page's first use of a feature: a broadcast appends\n// the first dismissing flash, a reply removes a row with an exit effect or\n// swaps in a form with show bindings and tag chips. Stream effects are\n// document-level (features/effects.js) and an exit effect runs BEFORE the\n// render; a swapped-in root connects AFTER it and would start its import\n// then, showing the server's rendering until the module arrives. Both are\n// avoided the same way: the render WAITS for the imports (`hold`), so the\n// stream is animated and the new root connects with its modules present.\n// Which modules a stream needs is read off the runtime's feature table\n// (unloadedFeaturesFor: every incoming reactive root's marker checks), plus\n// the effects module's own stream-level checks — no per-feature copy here.\n//\n// Turbo renders each <turbo-stream> on its own, so one waiting render would\n// let a later stream to the same target overtake it: while the hold is open\n// EVERY stream's render waits on the same promise, and so they resume in\n// arrival order. The hold is an animation's worth of patience, never the\n// import timeout: after STREAM_HOLD_MS the streams render plain, a root\n// connects and imports as usual, and the effects module, when it does arrive,\n// sweeps up the flashes it missed (their timers then start at arrival, not at\n// render). A module already loaded is never waited for.\nconst STREAM_HOLD_MS = 1000\nlet hold = null\nlet effects\nconst holding = new Set() // the modules the open hold is waiting for\n\nfunction loadIntoHold(name) {\n return loadReactiveFeature(name).then((feature) => {\n if (name !== \"effects\") return\n effects = feature\n feature?.sweep()\n })\n}\n\nonReactiveStreamWithoutEffects((event, loadedEffects) => {\n const detail = event.detail\n const render = detail?.render\n if (typeof render !== \"function\") return\n const streamEl = detail.newStream ?? event.target\n const names = unloadedFeaturesFor(incomingRoots(streamEl)).filter((name) => !holding.has(name))\n if (!loadedEffects && !holding.has(\"effects\") && streamNeedsEffects(streamEl)) names.push(\"effects\")\n // Nothing to wait for, and no hold open: a loaded effects module wraps the\n // stream now, as it would without this entry.\n if (names.length === 0 && !hold) return loadedEffects?.wrap(event)\n // A hold that is open grows: a later stream's modules join it (the hold\n // then lasts up to another STREAM_HOLD_MS from now), so a second stream is\n // never left to render without the module it asked for.\n if (names.length > 0) {\n for (const name of names) holding.add(name)\n const opened = (hold = Promise.race([\n Promise.all([hold, ...names.map(loadIntoHold)]),\n new Promise((resolve) => setTimeout(resolve, STREAM_HOLD_MS)),\n ]).then(() => {\n if (hold !== opened) return\n hold = null\n holding.clear()\n }))\n }\n const waiting = hold\n detail.render = async (streamElement) => {\n await waiting\n // Wrapped now, not when the event fired: nothing has rendered meanwhile.\n // On a copy: the event's detail.render is not ours to set back — a defer\n // reply has already rendered it, and leaves Turbo a no-op (issue #336).\n const held = { target: event.target, detail: { newStream: detail.newStream, render } }\n ;(loadedEffects ?? effects)?.wrap(held)\n await held.detail.render(streamElement)\n }\n})\n\n// The reactive roots a stream is about to put on the page: the roots in its\n// template (the template's own root element included), and the content\n// itself — read as part of the root already on the page it renders into,\n// which re-reads its markers once the render has run (issue #356; an editable\n// it brings, #351). Held for the module, the new content lands wired.\nfunction incomingRoots(streamEl) {\n const content = streamEl?.querySelector?.(\"template\")?.content\n if (!content) return []\n const roots = [...(content.querySelectorAll?.('[data-controller~=\"reactive\"]') ?? [])]\n for (const child of content.children ?? []) {\n if (child.matches?.('[data-controller~=\"reactive\"]') && !roots.includes(child)) roots.unshift(child)\n }\n roots.push(content)\n return roots\n}\n\n// Does this stream need the effects module? A cheap superset of what the\n// module itself resolves: a per-call effect, an effect declared on the target\n// or on incoming content, a dismissing flash arriving or already on the page.\nfunction streamNeedsEffects(streamEl) {\n return Boolean(\n streamEl?.hasAttribute?.(\"data-reactive-effect\") ||\n document\n .getElementById?.(streamEl?.getAttribute?.(\"target\"))\n ?.matches?.(\"[data-reactive-effect-update],[data-reactive-effect-exit]\") ||\n streamEl\n ?.querySelector?.(\"template\")\n ?.content?.querySelector?.(\"[data-reactive-effect-enter],[data-reactive-dismiss-after]\") ||\n document.querySelector?.(\"[data-reactive-dismiss-after]:not([data-reactive-dismiss-scheduled])\"),\n )\n}\n\n\nexport * from \"phlex/reactive/runtime\"\nexport default ReactiveController\n"
|
|
7
7
|
],
|
|
8
|
-
"mappings": "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",
|
|
9
|
-
"debugId": "
|
|
8
|
+
"mappings": "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",
|
|
9
|
+
"debugId": "9B15CE84F7802FED64756E2164756E21",
|
|
10
10
|
"names": []
|
|
11
11
|
}
|