{"_id":"fixi-js","_rev":"4-bc09514ba7a413368da3033c4769f54d","name":"fixi-js","dist-tags":{"latest":"0.9.4"},"versions":{"0.9.1":{"name":"fixi-js","version":"0.9.1","keywords":["fixi","htmx","hypermedia","fetch","html"],"author":{"name":"1cg"},"license":"BSD-0","_id":"fixi-js@0.9.1","maintainers":[{"name":"carsongross","email":"carson@bigsky.software"}],"homepage":"https://github.com/bigskysoftware/fixi#readme","bugs":{"url":"https://github.com/bigskysoftware/fixi/issues"},"dist":{"shasum":"f9fab8fb1b1adc9b796397d5117f2da8d026499e","tarball":"https://registry.npmjs.org/fixi-js/-/fixi-js-0.9.1.tgz","fileCount":3,"integrity":"sha512-1I1vja27zLaYGssSD7Qu0KqtVMgOTJzEd4WN6JuPUk0/sQ4TKxWoHdTKdljg3kCG2mQRNBKODrAsLLmBFkmZKQ==","signatures":[{"sig":"MEQCIGJOBkhizwR9nMJ3V/Wz22U1dUgeQu5m5jxxqjj1/I0QAiBJbjemCILWV15ru/iQXcfdzize7fA/E1X5P28pdO9+kQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":46198},"main":"surreal.js","gitHead":"09ce6716330a7ed8cc20629eeec95876b6a7a76e","_npmUser":{"name":"carsongross","email":"carson@bigsky.software"},"repository":{"url":"git+https://github.com/bigskysoftware/fixi.git","type":"git"},"_npmVersion":"10.8.2","description":"fixi.js - A Small Generalized Hypermedia Controls Tool","directories":{},"_nodeVersion":"20.19.4","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/fixi-js_0.9.1_1754340069138_0.14484018907556884","host":"s3://npm-registry-packages-npm-production"}},"0.9.2":{"name":"fixi-js","version":"0.9.2","keywords":["fixi","htmx","hypermedia","fetch","html"],"author":{"name":"1cg"},"license":"BSD-0","_id":"fixi-js@0.9.2","maintainers":[{"name":"carsongross","email":"carson@bigsky.software"}],"homepage":"https://github.com/bigskysoftware/fixi#readme","bugs":{"url":"https://github.com/bigskysoftware/fixi/issues"},"dist":{"shasum":"c987cd3a5829a6d4c3265aa91b23a4ebf019525c","tarball":"https://registry.npmjs.org/fixi-js/-/fixi-js-0.9.2.tgz","fileCount":3,"integrity":"sha512-bZVM8SrhsSi5lOkU0e9SjMeXrVpx9f/oo3bwn5E/agLb9GOW6fWo1MeUq90ZDg9z603cTpgWb3k3wGPdAyazdg==","signatures":[{"sig":"MEYCIQDOn2hFyE5V2B+CFXzDvurdC3zAaFcsLhHq5Wri3YnDIgIhAPx+xiZ49Uo6vw/mxhLmzMMtHUVlASGUpur9iKb880cj","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":46195},"main":"fixi.js","gitHead":"338e857244a9dd6c762a2beb8f0d6a99b05e7b61","_npmUser":{"name":"carsongross","email":"carson@bigsky.software"},"repository":{"url":"git+https://github.com/bigskysoftware/fixi.git","type":"git"},"_npmVersion":"10.8.2","description":"fixi.js - A Small Generalized Hypermedia Controls Tool","directories":{},"_nodeVersion":"20.19.4","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/fixi-js_0.9.2_1754340274341_0.8134179706238811","host":"s3://npm-registry-packages-npm-production"}},"0.9.3":{"name":"fixi-js","version":"0.9.3","keywords":["fixi","htmx","hypermedia","fetch","html"],"author":{"name":"1cg"},"license":"BSD-0","_id":"fixi-js@0.9.3","maintainers":[{"name":"carsongross","email":"carson@bigsky.software"}],"homepage":"https://github.com/bigskysoftware/fixi#readme","bugs":{"url":"https://github.com/bigskysoftware/fixi/issues"},"dist":{"shasum":"bc3f5a8429f1a222eb0bfc2443435696558307b4","tarball":"https://registry.npmjs.org/fixi-js/-/fixi-js-0.9.3.tgz","fileCount":3,"integrity":"sha512-jEgxm/tI/tCTaSAEKk8gIzvUGATu6SUtjILglZzcvktFv7LkOiWJPFrzQnIiZr4/4K/2KajV9kNbt6wifS8yog==","signatures":[{"sig":"MEQCIA6/xxSaF2J8V1Y2BZXMO6BDoLuhDDTB0B6AYEwXCMM8AiAOLn9okS2ALcb9TCsYYMpL8inLBV1/gQjf5ilxFuZbIw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":47229},"main":"fixi.js","gitHead":"665ad8a344f102418cba19a1a55c9638edc5c673","_npmUser":{"name":"carsongross","email":"carson@bigsky.software"},"repository":{"url":"git+https://github.com/bigskysoftware/fixi.git","type":"git"},"_npmVersion":"10.8.3","description":"fixi.js - A Small Generalized Hypermedia Controls Tool","directories":{},"_nodeVersion":"22.9.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/fixi-js_0.9.3_1773689107025_0.4373512037921008","host":"s3://npm-registry-packages-npm-production"}},"0.9.4":{"name":"fixi-js","version":"0.9.4","description":"fixi.js - A Small Generalized Hypermedia Controls Tool","main":"fixi.js","repository":{"type":"git","url":"git+https://github.com/bigskysoftware/fixi.git"},"author":{"name":"1cg"},"license":"BSD-0","keywords":["fixi","htmx","hypermedia","fetch","html"],"bugs":{"url":"https://github.com/bigskysoftware/fixi/issues"},"gitHead":"5d1fb81882e33f66c1704f008e7d3865bf531c1a","_id":"fixi-js@0.9.4","homepage":"https://github.com/bigskysoftware/fixi#readme","_nodeVersion":"25.9.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-ruUKWlM+t6k7YvoVO67z/1jMw04Q6rJCEBeZJPeFzt7U/pXz3S4bp+qxll+nwpOPN/f+pyTdLU70Ic5G+gyk1A==","shasum":"bdda084a131ccc7610b0d94ae063cf17329d785d","tarball":"https://registry.npmjs.org/fixi-js/-/fixi-js-0.9.4.tgz","fileCount":3,"unpackedSize":48891,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIEiR5ohPUtweVRAWOhTLoKnPLHdDu4yGGgtDImgvKYXqAiEAqWRTvUP4IpBWMuzmz/l7l2uqrnEfQEJZUkkleps6hTE="}]},"_npmUser":{"name":"carsongross","email":"carson@bigsky.software"},"directories":{},"maintainers":[{"name":"carsongross","email":"carson@bigsky.software"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/fixi-js_0.9.4_1777136887999_0.8983634166143613"},"_hasShrinkwrap":false}},"time":{"created":"2025-08-04T20:41:08.691Z","modified":"2026-04-25T17:08:08.243Z","0.9.1":"2025-08-04T20:41:09.322Z","0.9.2":"2025-08-04T20:44:34.508Z","0.9.3":"2026-03-16T19:25:07.171Z","0.9.4":"2026-04-25T17:08:08.144Z"},"bugs":{"url":"https://github.com/bigskysoftware/fixi/issues"},"author":{"name":"1cg"},"license":"BSD-0","homepage":"https://github.com/bigskysoftware/fixi#readme","keywords":["fixi","htmx","hypermedia","fetch","html"],"repository":{"type":"git","url":"git+https://github.com/bigskysoftware/fixi.git"},"description":"fixi.js - A Small Generalized Hypermedia Controls Tool","maintainers":[{"name":"carsongross","email":"carson@bigsky.software"}],"readme":"<h1>&#x1F6B2; fixi.js - <i>it ain't much...</i></h1> \n\n\n[fixi.js](https://swag.htmx.org/products/fixi-js-tee) is an experimental, minimalist implementation\nof [generalized hypermedia controls](https://dl.acm.org/doi/fullHtml/10.1145/3648188.3675127)\n\nfixi.js is the flagship library of the [fixi project](https://fixiproject.org), a family of minimalist companion libraries.\n\nThe fixi [api](#api) consists of six [HTML attributes](#attributes), nine [events](#events) & two [properties](#properties)\n\nHere is an example:\n\n```html\n<button fx-action=\"/content\" <!-- URL to issue request to -->\n        fx-method=\"get\"      <!-- HTTP Method to use -->\n        fx-trigger=\"click\"   <!-- Event that triggers the request -->\n        fx-target=\"#output\"  <!-- Element to swap -->\n        fx-swap=\"innerHTML\"> <!-- How to swap -->\n    Get Content\n</button>\n<output id=\"output\"></output>\n```\n\nWhen this fixi-powered `button` is clicked it  will issue an HTTP `GET` request to the \n`/content` [relative URL](https://www.w3.org/TR/WD-html40-970917/htmlweb.html#h-5.1.2) and swap the HTML content of \nthe response inside the `output` tag below it.\n\n## Minimalism\n\nPhilosophically, fixi is [scheme](https://scheme.org/) to [htmx](https://htmx.org)'s\n[common lisp](https://lisp-lang.org/): it is designed to be as \n[lean as possible](https://ia601608.us.archive.org/8/items/pdfy-PeRDID4QHBNfcH7s/LeanSoftware_text.pdf) while still \nbeing useful for real world projects.\n\nAs such, it doesn't have many of the features found in htmx, including:\n\n* [request queueing & synchronization](https://htmx.org/attributes/hx-sync/)\n* [extended selector support](https://htmx.org/docs/#extended-css-selectors)\n* [extended event support](https://htmx.org/docs/#special-events)\n* [attribute inheritance](https://htmx.org/docs/#inheritance)\n* [request indicators](https://htmx.org/docs/#indicators)\n* [CSS transitions](https://htmx.org/docs/#css_transitions)\n* [history support](https://htmx.org/docs/#history)\n\nfixi takes advantage of some modern JavaScript features not used by htmx:\n\n* [`async` functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)\n* The [`fetch()` API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)\n* The use of [`MutationObserver`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) for monitoring when new content is added\n* The [View Transition API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API) (used by htmx, but the sole mechanism for transitions in fixi)\n\nA hard constraint on the project is that the _unminified_, _uncompressed_ size must be less than that of\nthe minified & compressed version of the (excellent) [preact library](https://bundlephobia.com/package/preact) (currently 4.6Kb).\n\nThe current uncompressed size is `3473` bytes, the gzipped size is `1473` bytes and the brotli'd size is `1278` bytes,\nas determined by:\n\n```bash\nls -l fixi.js | awk  '{print \"raw:\", $5}'; gzip -k fixi.js; ls -l fixi.js.gz | awk  '{print \"gzipped:\", $5}'; rm fixi.js.gz; brotli fixi.js; ls -l fixi.js.br | awk  '{print \"brotlid:\", $5}'; rm fixi.js.br\n```\n\nAnother goal is that users should be able to [debug](https://developer.chrome.com/docs/devtools/javascript/) \nfixi easily, since it is small enough to use unminified.\n\nThe code style is [dense](fixi.js), but the statements are structured for debugging.\n\nLike a fixed-gear bike, fixi has very few moving parts:\n\n* No dependencies (including test and development)\n* No JavaScript API (beyond the [events](#events))\n* No minified `fixi.min.js` file\n* No `package.json`\n* No build step\n\nThe fixi project consists of four files:\n\n* [`fixi.js`](fixi.js), the code for the library\n* [`test.html`](test.html), the test suite for the library\n* This [`README.md`](README.md), which is the documentation\n* [`npm.sh`](npm.sh), which generates npm releases of the library\n\n[`test.html`](test.html) is a stand-alone HTML file that implements its own visual testing infrastructure, mocking for \n`fetch()`, etc. and that can be opened using the `file:` protocol for easy testing.\n\n## Installing\n\nfixi is designed to be easily [vendored](https://htmx.org/essays/vendoring/), that is, copied, into your project:\n\n```bash\ncurl https://raw.githubusercontent.com/bigskysoftware/fixi/refs/tags/0.9.4/fixi.js >> fixi-0.9.4.js\n```\n\nThe SHA256 of v0.9.4 is \n\n`rPuE58bCyMrZ37o4bByX4epgBA1bAeTylp7TxOXVh90=`\n\ngenerated by the following command line script:\n\n```bash\ncat fixi.js | openssl sha256 -binary | openssl base64\n```\nAlternatively can download the source from here:\n\n<https://github.com/bigskysoftware/fixi/archive/refs/tags/0.9.4.zip>\n\nYou can also use the JSDelivr CDN for local development or testing:\n\n```html\n<script src=\"https://cdn.jsdelivr.net/gh/bigskysoftware/fixi@0.9.4/fixi.js\"\n        crossorigin=\"anonymous\"\n        integrity=\"sha256-Qalmv6iqaAnrglOx2AWSoLfJFmXLGtjAIdBgsLc3bgg=\"></script>\n```\n\nFinally, fixi is available on NPM as the [`fixi-js`](https://www.npmjs.com/package/fixi-js) package.\n\n## Support\n\nYou can get support for fixi via:\n\n* [Github Issues](https://github.com/bigskysoftware/fixi/issues)\n* [The htmx Discord `#fixi` channel](https://htmx.org/discord)\n\n## API\n\n### Attributes\n\n<table>\n<thead>\n<tr>\n  <th>attribute</th>\n  <th>description</th>\n  <th>example</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n  <td>\n    <code>fx-action</code>\n  </td>\n  <td>\n    The URL to which an HTTP request will be issued, required\n  </td>\n  <td>\n    <code>fx-action='/demo'</code>\n  </td>\n</tr>\n<tr>\n<td><code>fx-method</code></td>\n<td>The HTTP Method that will be used for the request (case-insensitive), defaults to <code>GET</code></td>\n<td><code>fx-method=&#39;DELETE&#39;</code></td>\n</tr>\n<tr>\n<td><code>fx-target</code></td>\n<td>A CSS selector specifying where to place the response HTML in the DOM, defaults to the current element</td>\n<td><code>fx-target=&#39;#a-div&#39;</code></td>\n</tr>\n<tr>\n<td><code>fx-swap</code></td>\n<td>A string specifying how the content should be swapped into the DOM, can be one of <code>innerHTML</code>, <code>outerHTML</code>, <code>beforebegin</code>, <code>afterbegin</code>, <code>beforeend</code>, <code>afterend</code>, <code>none</code>, or any valid property on the element (e.g. `className` or `value`), defaults to  <code>outerHTML</code></td>\n<td><code>fx-swap=&#39;innerHTML&#39;</code></td>\n</tr>\n<tr>\n<td><code>fx-trigger</code></td>\n<td>The event that will trigger a request, defaults to <code>submit</code> for <code>form</code> elements, <code>change</code> for <code>input</code>-like elements & <code>click</code> for all other elements</td>\n<td><code>fx-trigger=&#39;click&#39;</code></td>\n</tr>\n<tr>\n<td><code>fx-ignore</code></td>\n<td>Any element with this attribute on it or on an ancestor will not be processed for <code>fx-*</code> attributes</td>\n<td></td>\n</tr>\n</tbody>\n</table>\n\n#### Modus Operandi\n\nfixi works in a straight-forward manner & I encourage you to look at [the source](fixi.js) as you read through this. The \nthree components of fixi are:\n\n* [Processing](#processing) elements in the DOM (or added to the DOM)\n* Issuing HTTP [requests](#requests) in response to events\n* [Swapping](#swapping) new HTML content into the DOM\n\n##### Processing\n\nThe main entry point is found at the bottom of [fixi.js](fixi.js): on the `DOMContentLoaded` event fixi does two things:\n\n* It has a MutationObserver begin to watch for newly added content with fixi-powered elements\n* It processes any existing fixi-powered elements\n\nfixi-powered elements are elements with the `fx-action` attribute on them.\n\nWhen fixi finds one it will establish an event listener on that element that will dispatch an AJAX request via \n`fetch()` to the URL specified by `fx-action`.\n\nfixi will ignore any elements that have the `fx-ignore` attribute on them or on an ancestor.\n\nThe event that will trigger the request is determined by the `fx-trigger` attribute.\n\nIf that attribute is not present, the trigger defaults to:\n\n* `submit` for `form` elements\n* `change` for `input:not([type=button])`, `select` & `textarea` elements\n* `click` for everything else.\n\n##### Requests\n\nWhen a request is triggered, the [HTTP method](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) of the request \nwill be determined by the `fx-method` attribute.  If this attribute is not present, it will default to `GET`.  This \nattribute is case-insensitive.\n\nfixi sends the [request header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers) `FX-Request`, with the value \n`true`.  You can add or remove headers using the `evt.detail.cfg.headers` object, see the [`fx:config`](#fxconfig) event \nbelow.\n\nIf an element is within a form element or has a `form` attribute, the values of that form will be included with the\nrequest.  Otherwise, if the element has a `name`, its `name` & `value` will be sent with the request.   You can add or \nremove values using the `evt.detail.cfg.form` `FormData` object in the [`fx:config`](#fxconfig) event.\n\n`GET` & `DELETE` requests will include values via query parameters, other request types will submit them as a form\nencoded body.\n\nBefore a request is sent, the aforementioned [`fx:config`](#fxconfig) event is triggered, which can be used to configure\naspects of the request. If `preventDefault()` is invoked in this event, the request will not be sent.\nThe `evt.detail.cfg.drop` property will be [truthy](https://developer.mozilla.org/en-US/docs/Glossary/Truthy)\nif there is an existing outstanding request associated with the element, otherwise the value will be\n[falsy](https://developer.mozilla.org/en-US/docs/Glossary/Falsy). More specifically, the value is equal\nto the number of outstanding requests associated with the element. If the value is truthy after the last\n`fx:config` handler has ran to completion, the request will be dropped (i.e. not issued). This implies,\nif you do not [customize the behavior](#replace-existing-requests-in-flight), an element will drop all\nnew requests whilst there is an outstanding request associated with it.\n\nIn the [`fx:config`](#fxconfig) event you can also set the `evt.detail.cfg.confirm` property to a no-argument function.\nThis function can return a Promise and can be used to asynchronously confirm that the request should be issued:\n\n```js\nfunction showAsynConfirmDialog() {\n    //... a Promise-based confirmation dialog...\n}\n\ndocument.addEventListener(\"fx:config\", (evt) => {\n    evt.detail.cfg.confirm = showAsynConfirmDialog;\n})\n```\n\nNote that confirmation will only occur if the [`fx:config`](#fxconfig) event is not canceled and the request is not\ndropped.\n\nAfter the configuration step and the confirmation, if any, the [`fx:before`](#fxbefore) event will be triggered,\nand then a `fetch()` will be issued.  The `evt.detail.cfg` object from the events above  is passed to the `fetch()` \nfunction as the second [RequestInit](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit) argument.\n\nWhen fixi receives a response it triggers the [`fx:after`](#fxafter) event.\n\nIn this event there are two more properties available on `evt.detail.cfg`:\n\n* `response` the fetch [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) object\n* `text` the text of the response\n\nThese can be inspected, and the `text` value can be changed if you want to transform it in some way.\n\nIf a network error occurs the [`fx:error`](#fxerror) event will be triggered instead of `fx:after`, and nothing\nwill be swapped.\n\nNote that `fetch()` only triggers errors \n[when a request fails due to a bad URL or network error](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch),\nso valid HTTP responses with non-`200` response codes will not trigger an error.  \n\nIf you wish to handle non-200 reponses differently you should check the response code in the `fx:after` event:\n\n```js\ndocument.addEventListener(\"fx:after\", (evt)=>{\n  // rewire 404s to the body, remove current head so new head can replace it\n  if (evt.detail.cfg.response.status == 404){\n    document.head.remove()\n    evt.detail.cfg.target = document.body\n    evt.detail.cfg.swap = 'outerHTML'\n  }\n})\n```\n\nThe [`fx:finally`](#fxfinally) event will be triggered regardless if an error occurs or not.\n\n##### Swapping\n\nfixi then swaps the response text into the DOM using the mechanism specified by `fx-swap`, targeting the element specified\nby `fx-target`.  If the `fx-swap` attribute is not present, fixi will use `outerHTML`.\n\nIf the `fx-target` attribute is not present, it will target the element making the request.  \n\nThe swap mechanism and target can be changed in the request-related fixi events.\n\nYou can implement a custom swapping mechanism by setting a function into the `evt.detail.cfg.swap` property in one of\nthe request related events.  This function should take one argument that will be set to the fixi request config itself.  \nOn that object you can access the `target`, `text`, `request`, etc.  You can see \nan [example below](#custom-swapping-algorithms) showing how to do this.\n\nBy default, swapping will occur in a [View Transition](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API)\nif they are available.  If you don't want this to occur, you can set the `evt.detail.cfg.transition` property to false\nin one of the request-related events.\n\nFinally, when the swap and any associated View Transitions have completed, the `fx:swapped` event will be triggered on \nthe element.  If the element has been removed from the DOM, the event will also be triggered on `document`, which allows\nyou to listen for this event on `document` and receive it after every swap.\n\n###### Notes on Targeting the Document Element (`html`)\n\nNote that if you want to replace the entire document (that is, target the `html` element) you _must_ use an `innerHTML`\nswap, because the default `outerHTML` swap will fail with a [`NoModificationAllowed`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException#nomodificationallowederror)\nerror.\n\nIf you wish to default the swap mechanism to `innerHTML` when targeting the `html` you can use this code:\n\n```js\ndocument.addEventListener(\"fx:config\", (evt) => {\n    if (evt.detail.cfg.target === document.documentElement){\n        evt.detail.cfg.swap = \"innerHTML\"\n    }\n})\n```\n\nNote also that if you replace the `head` tag, due to the [quirks of the DOM API specification](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML#security_considerations), \nstyles _will_ be updated, but _scripts_ will not be executed.  \n\nYou must therefore manually manage and execute any script tags:\n\n```js\ndocument.addEventListener(\"fx:config\", (evt) => {\n    if (evt.detail.cfg.target === document.documentElement){\n        let scripts = document.head.querySelectorAll(\"script\");\n        for(let script of scripts) { \n            let newScript = document.createRange().createContextualFragment(script.outerHTML)\n            document.head.insertBefore(newScript, script)\n            script.remove()\n        }\n    }\n})\n```\n\nFor inline scripts where [Locality of Behavior](https://htmx.org/essays/locality-of-behaviour) is desired. \nThe target scripts need to be replaced in order to execute.  \n\nThis will automatically execute all script tags that are swapped in:\n\n```js\ndocument.addEventListener('fx:swapped', (evt) => {\n    evt.detail.cfg.target.querySelectorAll('script').forEach(s =>\n        s.replaceWith(Object.assign(document.createElement('script'),{textContent:s.textContent}))\n    )\n})\n```\n\nHowever, these simple approaches may fail if you have scripts that, for example, create global variables with `let`, etc.\n\nFor this reason we broadly recommend loading all your scripts up front or simply using anchor tags for full page \nnavigations, unless you want to get into the weeds of dealing with these issues.\n\n#### Complete Example\n\nHere is a complete example using all the attributes available in fixi:\n\n```html\n<button fx-action=\"/demo\" fx-method=\"GET\" \n        fx-target=\"#output\" fx-swap=\"innerHTML\" \n        fx-trigger=\"click\">\n    Get Content\n</button>\n<output id=\"output\" fx-ignore>--</output>\n```\n\nIn this example, the button will issue a `GET` request to `/demo` and put the resulting HTML into the `innerHTML` of the\noutput element with the id `output`.\n\nBecause the `output` element is marked as `fx-ignore`, any `fx-action` attributes in the new content will be ignored.\n\n### Events\n\nfixi fires the following events, broken into two categories:\n\n<table>\n<thead>\n<tr>\n  <th>category</th>\n  <th>event</th>\n  <th>description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td rowspan=\"3\"><a href=\"#initialization-events\">initialization</a></td>\n<td>\n<a href=\"#fxinit\"><code>fx:init</code></a>\n</td>\n<td>\ntriggered on elements that have a <code>fx-action</code> attribute and are about to be initialized by fixi\n</td>\n</tr>\n<tr>\n<td>\n<a href=\"#fxinited\"><code>fx:inited</code></a>\n</td>\n<td>\ntriggered on elements have been initialized by fixi (does <b>not</b> bubble)\n</td>\n</tr>\n<tr>\n<td>\n<a href=\"#fxprocess\"><code>fx:process</code></a>\n</td>\n<td>\nfixi listens on the <code>document</code> object for this event and will process (that is, enable any fixi-powered \nelements) within that element.\n</td>\n</tr>\n<tr>\n<td rowspan=\"6\"><a href=\"#fetch-events\">fetch</a></td>\n<td>\n<a href=\"#fxconfig\"><code>fx:config</code></a>\n</td>\n<td>\ntriggered on an element immediately when a request has been triggered, allowing users to configure the request\n</td>\n</tr>\n<tr>\n<td>\n<a href=\"#fxbefore\"><code>fx:before</code></a>\n</td>\n<td>\ntriggered on an element just before a <code>fetch()</code> request is made\n</td>\n</tr>\n<tr>\n<td>\n<a href=\"#fxafter\"><code>fx:after</code></a>\n</td>\n<td>\ntriggered on an element just after a <code>fetch()</code> request finishes normally but before content is swapped\n</td>\n</tr>\n<tr>\n<td>\n<a href=\"#fxerror\"><code>fx:error</code></a>\n</td>\n<td>\ntriggered on an element if something is thrown from a <code>fetch()</code>\n</td>\n</tr>\n<tr>\n<td>\n<a href=\"#fxfinally\"><code>fx:finally</code></a>\n</td>\n<td>\ntriggered on an element after a request no matter what\n</td>\n</tr>\n<tr>\n<td>\n<a href=\"#fxswapped\"><code>fx:swapped</code></a>\n</td>\n<td>\ntriggered after the swap and any associated \n<a href=\"https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API\">View Transition</a> has completed\n</td>\n</tr>\n</tbody>\n</table>\n\n#### Initialization Events\n\n##### `fx:init`\n\nThe `fx:init` event is triggered when fixi is processing a node with an `fx-action` attribute. One property\nis available in `evt.detail`:\n\n* `options` - An [Options Object](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#options)\n              that will be passed to `addEventListener()`\n\nIf this event is cancelled via `preventDefault()`, the element will not be initialized by fixi.\n\n##### `fx:inited`\n\nThe `fx:inited` event is triggered when fixi finished processing a node with an `fx-action` attribute.\n\nUnlike other fixi events, this event does not bubble.\n\n##### `fx:process`\n\nfixi listens for the `fx:process` event on the `document` and will enable any unprocessed fixi-powered elements within\nthe element as well as the element itself.  \n\n#### Fetch Events\n\nfixi uses the [`fetch()` API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to issue HTTP requests. It\ntriggers events around this call that allow users to configure the request.\n\n##### `fx:config`\n\nThe first event triggered is `fx:config`. This event can be used to configure the arguments passed to `fetch()` via the\nfixi config object, which can be found at `evt.detail.cfg`.\n\nThis config object has the following properties:\n\n* `trigger` - The event that triggered the request\n* `method` - The HTTP Method that is going to be used\n* `action` - The URL that the request is going to be issued to\n* `headers` - An Object of name/value pairs to be sent as HTTP Request Headers\n* `target` - The target element that will be swapped when the response is processed\n* `swap` - The mechanism by which the element will be swapped\n* `body` - The body of the request, if present, a FormData object that holds the data of the form associated with the request\n* `drop` - Whether this request will be dropped, defaults to the number of outstanding requests associated with the element\n* `transition` - The View Transition function, if it is available.  Set to `false` if you don't want a transition to occur\n* `preventTrigger` - A boolean (defaults to `true`) that, if true, will call `preventDefault()` on the triggering event\n* `signal` - The AbortSignal of the related AbortController for the request\n* `abort()` - A function that can be invoked to abort the pending fetch request\n* `fetch()` - The fetch() function that will be used for the request, can be used for [mocking](#mocking) requests\n\nMutating the `method`, etc. properties of the `cfg` object will change the behavior of the request dynamically. Note\nthat the `cfg` object is passed to `fetch()` as the second argument of type `RequestInit`, so any properties you want\nto set on the `RequestInit` may be set on the `cfg` object (e.g. `credentials`).\n\nAnother property available on the `detail` of this event is `requests`, which will be a \n[Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) of any existing\noutstanding requests for the element.\n\n###### replace existing requests in flight\nfixi does not implement request queuing like htmx does, but you can implement a simple\n\"replace existing requests in flight\" rule with the following JavaScript:\n\n```js\ndocument.addEventListener(\"fx:config\", (evt) => {\n    evt.detail.cfg.drop = 0;  // allow this request to be issued even if there are other requests\n    evt.detail.requests.forEach((cfg) => cfg.abort()); // abort all existing requests\n})\n```\n\nIf you call `preventDefault()` on this event, no request will be issued.\n\n##### `fx:before`\n\nThe  `fx:before` event is triggered just before a `fetch()` is issued. The config will again be available in the\n`evt.detail.cfg` property, but after any confirmation is done.  The requests will also be available in \n`evt.detail.requests` and will include the current request.\n\nIf you call `preventDefault()` on this event, no request will be issued.\n\n##### `fx:after`\n\nThe  `fx:after` event is triggered after a `fetch()` successfully completes. The config will again be available in the\n`evt.detail.cfg` property, and will have two additional properties:\n\n* `response` - The response object from the `fetch()` call\n* `text` - The text of the response\n\nAt this point you may still mutate the `swap`, etc. attributes to affect swapping, and you may mutate the `text` if you\nwant to modify it is some way before it is swapped.\n\nCalling `preventDefault()` on this event will prevent swapping from occurring.\n\n##### `fx:error`\n\nThe  `fx:error` event is triggered when a network error occurs or when the request is aborted using the\n`abort` function on the config. In this case the `evt.detail.cfg` object is available for modification\nand `cfg.response` and `cfg.text` will not be present. The `evt.detail.error` property contains the\nthrown value. If you receive this event, swapping will not occur, the processing is terminated early.\n\n##### `fx:finally`\n\nThe  `fx:finally` event is triggered regardless if an error occurs or not and can be used to clean up after a request.\nAgain the `evt.detail.cfg` object is available for modification.\n\n##### `fx:swapped`\n\nThe  `fx:swapped` event is triggered once the swap and any associated View Transitions complete.  The \n`evt.detail.cfg` object is available.\n\n### Properties\n\nfixi adds two properties to elements in the DOM\n\n<table>\n<thead>\n<tr>\n  <th>property</th>\n  <th>description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>\n<a href=\"#document__fixi_mo\"><code>document.__fixi_mo</code></a>\n</td>\n<td>\nThe MutationObserver that fixi uses to watch for new content to process new fixi-powered elements.\n</td>\n</tr>\n<tr>\n<td>\n<a href=\"#elt__fixi\"><code>elt.__fixi</code></a>\n</td>\n<td>\nThe event handler created by fixi on fixi-powered elements \n</td>\n</tr>\n</tbody>\n</table>\n\n#### `document.__fixi_mo`\n\nfixi stores the Mutation Observer that it uses to watch for new content in the `__fixi_mo` property on the `document`.\n\nYou can use this property to temporarily disable mutation observation for performance reasons:\n\n```js\n// disable processing\ndocument.__fixi_mo.disconnect()\n\n/* ... heavy mutation code that should not be processed by fixi */\n\n// reenable processing\ndocument.__fixi_mo.observe(document.body, {childList:true, subtree:true})\n```\nSimilar code can be used to adjust the MutationObserver to listen for mutations in some subset of the document.\n\nFinally, you can also switch to entirely manual processing using the [`fx:after`](#fxafter), \n[`fx:swapped`](#fxswapped) & [`fx:process`](#fxprocess) events:\n\n```js\ndocument.__fixi_mo.disconnect()\ndocument.addEventListener(\"fx:after\", (evt)=>{\n  // capture the parent element of the target in the config before swapping\n  evt.detail.cfg.parent = evt.detail.cfg.target.parentElement\n})\ndocument.addEventListener(\"fx:swapped\", (evt)=>{\n  // reprocess the parent\n  evt.detail.cfg.parent.dispatchEvent(new CustomEvent(\"fx:process\"), {bubbles:true})\n})\n```\n\n#### `elt.__fixi`\n\nThe `__fixi` property will be added to any element that has an `fx-action` attribute on it assuming that the element\nor an ancestor is not marked `fx-ignore`.  \n\nThe value of the property will be the event listener that is added to the element.  It also has two properties:\n\n* `evt` - the string event name that will trigger the handler\n* `requests` - the config values of any open requests (may be `null`)\n\nThis property can be used to remove the fixi-generated event handler like so:\n\n```js\nelt.removeEventListener(elt.__fixi.evt, elt.__fixi)\n```\n\nIf you want to reprocess the element you will need to remove the property entirely and trigger the \n[`fx:process`](#fxprocess) event on it:\n\n```js\nelt.removeEventListener(elt.__fixi.evt, elt.__fixi)\ndelete elt.__fixi\nelt.dispatchEvent(new CustomEvent(\"fx:process\"), {bubbles:true})\n```\n\nYou can also use this property to store extension-related information.  See the [polling example](#polling) below.\n\n### Defaults via `window.fixiCfg`\n\nYou can change a few of fixi's per-request defaults by assigning a global `fixiCfg` object.\n\n```html\n<script>\n    var fixiCfg = {\n        swap: \"morph\",\n        transition: false,\n        headers: {\"X-CSRF-Token\": \"abc123\"},\n    }\n</script>\n<script src=\"fixi.js\"></script>\n```\n\n<table>\n<thead>\n<tr><th>key</th><th>effect</th></tr>\n</thead>\n<tbody>\n<tr>\n<td><code>swap</code></td>\n<td>\nDefault value for <code>cfg.swap</code> when an element has no <code>fx-swap</code> attribute\n</td>\n</tr>\n<tr>\n<td><code>transition</code></td>\n<td>\nReplaces the default <code>document.startViewTransition</code> binding.  Set to <code>false</code>\nto disable view transitions entirely.  Set to a function with the same shape as\n<code>startViewTransition</code> to plug in a custom wrapper.\n</td>\n</tr>\n<tr>\n<td><code>headers</code></td>\n<td>\nMerged into the default <code>{\"FX-Request\": \"true\"}</code> headers; user-supplied keys win on\ncollision.\n</td>\n</tr>\n</tbody>\n</table>\n\nNote that any [`fx:config`](#fxconfig) listener can override these defaults by mutating `evt.detail.cfg`, so per-element behavior is unaffected.\n\n## Mocking\n\nIt is easy to mock `fetch()` requests in fixi by replacing the `evt.detail.cfg.fetch` property with a mocking function.\nThe function can take the same arguments as [`fetch()`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) (or not\nif they are not needed) and should return a [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)\ncompatible object or a Promise that resolves to one.\n\nFor the simple case, the return object need only implement the `.text()` method.\n\nHere is an example that mocks responses using `template` elements\n\n```js\ndocument.addEventListener(\"fx:config\", (evt) => {\n   const template = document.getElementById(evt.detail.cfg.action)\n   if (template) {\n     evt.detail.cfg.fetch = ()=>({text:  ()=>template.innerHTML}) // note the parens to make {} an object\n   }\n })\n```\n```html\n<button fx-action=\"/demo\">\n    Replace Me With A Template\n</button>\n<template id=\"/demo\">\n    Some Template Content...\n</template>\n```\n\n## Examples\n\nHere are some basic examples of fixi in action\n\n### Click To Edit\n\nThe htmx [click to edit example](https://htmx.org/examples/click-to-edit/) can be easily ported to fixi:\n\n```html\n<div id=\"target-div\">\n    <div><label>First Name</label>: Joe</div>\n    <div><label>Last Name</label>: Blow</div>\n    <div><label>Email</label>: joe@blow.com</div>\n    <button fx-action=\"/contact/1/edit\" fx-target=\"#target-div\" class=\"btn primary\">\n    Click To Edit\n    </button>\n</div>\n```\n\n### Delete Row\n\nThe [delete row example](https://htmx.org/examples/delete-row/) from htmx can be implemented in fixi like so:\n\n```html\n<tr id=\"row-1\">\n  <td>Angie MacDowell</td>\n  <td>angie@macdowell.org</td>\n  <td>Active</td>\n  <td>\n    <button class=\"btn danger\" fx-action=\"/contact/1\" fx-method=\"delete\" fx-target=\"#row-1\">\n      Delete\n    </button>\n  </td>\n</tr>\n```\n\nNote that this version does not have a confirmation prompt, you would need to implement that yourself using the\n[`fx:config`](#fxconfig) event.\n\n### Lazy Loading\n\nThe htmx [lazy loading](https://htmx.org/examples/lazy-load/) example can be ported to fixi using the \n[`fx:inited`](#fxinited) event:\n\n```html\n<div fx-action=\"/lazy-content\" fx-trigger=\"fx:inited\">\n  Content Loading...\n</div>\n```\n\n## Cross-Origin Requests\n\nfixi uses the browser's [`fetch()` API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) defaults, which\nallow cross-origin requests. This means `fx-action` can point to any URL, including external domains.\n\nIf you want to restrict requests to same-origin only, you can set `mode: \"same-origin\"` on the config object via\nthe `fx:config` event:\n\n```js\ndocument.addEventListener(\"fx:config\", (evt)=>{\n    evt.detail.cfg.mode = \"same-origin\"\n})\n```\n\nYou can also enforce this at the browser level with the\n[`Content-Security-Policy: connect-src`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/connect-src)\nHTTP header, which restricts all `fetch()` (and XHR, WebSocket, etc.) connections to the specified origins:\n\n```http\nContent-Security-Policy: connect-src 'self'\n```\n\n## Extensions\n\nBecause fixi is minimalistic the user is responsible for implementing many behaviors they want via events. We have\nalready seen how to abort an existing request that is already in flight.  \n\nA suggested convention when you are adding fixi extension attributes is to use the `ext-fx` prefix, and to process the extension\nin the `fx:init` method.  You may find it useful to use the `__fixi` property on the element to store values necessary\nfor the extension to work.\n\nAnother suggested convention is keeping your fixi extensions in a single file called `ext-fixi.js` alongside your\n`fixi-<version>.js` file.\n\nHere are some examples of useful fixi extensions implemented using events.\n\n### Disabling an Element During A Request\n\nHere is an example that will use attributes to disable an element when a request is in flight:\n\n```js\n// fixi disable elements extension\ndocument.addEventListener(\"fx:init\", (evt)=>{\n  if (evt.target.matches(\"[ext-fx-disable]\")){\n    var disableSelector = evt.target.getAttribute('ext-fx-disable')\n    evt.target.addEventListener('fx:before', ()=>{\n      let disableTarget = disableSelector == \"\" ? evt.target : document.querySelector(disableSelector)\n      disableTarget.disabled = true\n      evt.target.addEventListener('fx:after', (afterEvt)=>{\n        if (afterEvt.target == evt.target){\n          disableTarget.disabled = false\n        }\n      })\n    })\n  }\n})\n```\n```html\n<button fx-action=\"/demo\" ext-fx-disable>\n  Demo\n</button>\n```\n\n### Showing an Indicator During A Request\n\nHere is an example that will use attributes a `fixi-request-in-flight` class to show an indicator of some kind:\n\n```js\n// fixi request indicator extension\ndocument.addEventListener(\"fx:init\", (evt)=>{\n  if (evt.target.matches(\"[ext-fx-indicator]\")){\n    var disableSelector = evt.target.getAttribute(\"ext-fx-indicator\")\n    evt.target.addEventListener(\"fx:before\", ()=>{\n      let disableTarget = disableSelector == \"\" ? evt.target : document.querySelector(disableSelector)\n      disableTarget.classList.add(\"fixi-request-in-flight\")\n      evt.target.addEventListener(\"fx:after\", (afterEvt)=>{\n        if (afterEvt.target == evt.target){\n          disableTarget.classList.remove(\"fixi-request-in-flight\")\n        }\n      })\n    })\n  }\n})\n```\n```html\n<style>\n  #indicator {\n    display: none;\n  }\n  #indicator.fixi-request-in-flight {\n    display: inline-block;\n  }\n</style>\n<button fx-action=\"/demo\" ext-fx-indicator=\"#indicator\">\n    Demo\n   <img src=\"spinner.gif\" id=\"indicator\">\n</button>\n```\n\nThis example can be modified to use classes or other mechanisms for showing indicators as well.\n\n### Debouncing A Request\n\nThe following extension allows you to [debounce](https://www.geeksforgeeks.org/debouncing-in-javascript/) the\ntriggering event for a fixi-powered element.  It does this by removing the initial listener installed by fixi and\nwiring in a new listener for the same event that delegates to the fixi handler if no other events occur in the\ngiven time period.  The debouncing time is specified via the `ext-fx-debounce` attribute, which specified the number of\nmilliseconds to wait before triggering the request.\n\n```js\n// fixi event debouncing extension\ndocument.addEventListener(\"fx:init\", (evt)=>{\n    let target = evt.target\n    // if this element has the debounce extention\n    if (target.hasAttribute(\"ext-fx-debounce\")){\n        // add a listener for the fx:inited event, when the __fixi property is available\n        target.addEventListener(\"fx:inited\", ()=>{\n            // remove the default listener \n            target.removeEventListener(target.__fixi.evt, target.__fixi)\n            let debounceTime = parseInt(target.getAttribute(\"ext-fx-debounce\"))\n            let timeout = null\n            // install a debounced version that delegates to the default listener \n            target.addEventListener(target.__fixi.evt, (evt)=>{\n                clearTimeout(timeout)\n                timeout = setTimeout(()=>target.__fixi(evt), debounceTime)\n            })\n        })\n    }\n})\n```\n\nHere is an implementation of the [active search](https://htmx.org/examples/active-search/) example from the htmx website\nusing this extension:\n\n```html\n<form action=\"/search\" fx-action=\"/search\" fx-target=\"#results\" fx-swap=\"innerHTML\">\n  <input id=\"search\" type=\"search\" fx-action=\"/search\" fx-trigger=\"input\" ext-fx-debounce=\"200\" fx-target=\"#results\" fx-swap=\"innerHTML\"/>\n  <button type=\"submit\">\n    Search\n  </button>\n</form>\n<table>\n  ...\n  <tbody id=\"results\">\n  ...\n  </tbody>\n</table>\n```\n\n### Polling\n\nhtmx-style polling can be implemented in the following manner:\n\n```js\n// fixi polling extension\ndocument.addEventListener(\"fx:init\", (evt)=>{\n  let elt = evt.target\n  if (elt.matches(\"[ext-fx-poll-interval]\")){\n    // wait for the non-bubbling fx:inited event on the element so the __fixi property is available\n    elt.addEventListener(\"fx:inited\", ()=>{\n        // squirrel away in case we want to call clearInterval() later\n        elt.__fixi.pollInterval = setInterval(()=>{\n            elt.dispatchEvent(new CustomEvent(\"poll\"))\n        }, parseInt(elt.getAttribute(\"ext-fx-poll-interval\")))\n    })\n  }\n})\n```\n```html\n<h3>Live News</h3>\n<div fx-action=\"/news\" fx-trigger=\"poll\" ext-fx-poll-interval=\"300\">\n  ... initial content ...\n</div>\n```\n\n### Server Sent Events\n\nFor [SSE](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)-based swaps,\nreach for [ssexi.js](https://github.com/bigskysoftware/ssexi), the fixi-project SSE companion.\nIt hooks into fixi's `fx:config` event and streams responses with\n`Content-Type: text/event-stream` directly into the target as each message arrives:\n\n```html\n<script src=\"fixi.js\"></script>\n<script src=\"ssexi.js\"></script>\n\n<button fx-action=\"/stream\" fx-swap=\"beforeend\" fx-target=\"#log\">Start</button>\n<div id=\"log\"></div>\n```\n\nOn the server, a message is any SSE `data:` frame containing HTML:\n\n```\ndata: <p>First update</p>\n\ndata: <p>Second update</p>\n\n```\n\nssexi also supports named events, JSON-targeted events, `cfg.sseReconnect` for automatic\nreconnection with `Last-Event-ID`, and `cfg.ssePauseOnHidden` to pause when the tab is\nhidden. See the [ssexi README](https://github.com/bigskysoftware/ssexi) for details.\n\nIf you'd rather roll your own, fixi's `fx:config` event gives you all the hooks you need;\nthe [ssexi source](https://github.com/bigskysoftware/ssexi/blob/main/ssexi.js) is a short\nreference implementation.\n\n### Confirmation\n\nThis extension implements a simple `confirm()` based confirmation if the `ext-fx-confirm` attribute is found.  Note that\nit does not use a Promise, just the regular old blocking `confirm()` function\n\n```js\n// fixi confirmation extension\ndocument.addEventListener(\"fx:config\", (evt)=>{\n    var confirmationMessage = evt.target.getAttribute(\"ext-fx-confirm\")\n    if (confirmationMessage){\n        evt.detail.cfg.confirm = ()=>confirm(confirmationMessage)\n    }\n})\n```\n```html\n<button fx-action=\"/demo\" fx-method=\"delete\" ext-fx-confirm=\"Are you sure?\">\n    Delete It\n</button>\n```\n\n### Relative Selectors\n\nThis extension implements relative selectors for the `fx-target` attribute.\n\n```js\n// fixi relative selectors extension\ndocument.addEventListener('fx:config', (evt)=>{\n    console.log(\"here\")\n    var target = evt.target.getAttribute(\"fx-target\") || \"\"\n    if (target.indexOf(\"closest \") == 0){\n        evt.detail.cfg.target = evt.target.closest(target.substring(8))\n    } else if (target.indexOf(\"find \") == 0){\n        evt.detail.cfg.target = evt.target.querySelector(target.substring(5))\n    } else if (target.indexOf(\"next \") == 0){\n        var matches = Array.from(document.querySelectorAll(target.substring(5)))\n        evt.detail.cfg.target = matches.find((elt) => evt.target.compareDocumentPosition(elt) === Node.DOCUMENT_POSITION_FOLLOWING)\n    } else if (target.indexOf(\"previous \") == 0){\n        var matches = Array.from(document.querySelectorAll(target.substring(9))).reverse()\n        evt.detail.cfg.target = matches.find((elt) => evt.target.compareDocumentPosition(elt) === Node.DOCUMENT_POSITION_PRECEDING)\n    }\n})\n```\n```html\n<button fx-action=\"/demo\" fx-target=\"next output\">\n    Get Data\n</button>\n<output></output>\n```\n\n### Intersection Events\n\nfixi does not trigger events when elements become visible like htmx does, but you can implement this behavior with the\nfollowing extension.  It adds an [IntersectionObserver](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)\nfor every element with the `intersect` trigger and wires it in to trigger that event once the element intersects\nthe viewport.  \n\n```js\n// fixi intersection events extension\ndocument.addEventListener(\"fx:init\", (evt) => {\n    let trigger = evt.target.getAttribute(\"fx-trigger\")\n    if(trigger === \"intersect\") {\n        let obs = evt.target.__fixi_ob = new IntersectionObserver((entries)=>{\n            for(const entry of entries) {\n                if (entry.isIntersecting){\n                    // done observing, remove\n                    obs.unobserve(evt.target)\n                    evt.target.__fixi_ob = null;\n                    // trigger event\n                    evt.target.dispatchEvent(new CustomEvent(\"intersect\"))\n                    return;\n                }\n            }\n        })\n        obs.observe(evt.target)\n    }\n})\n```\n\nWith this extension it is possible to implement the [infinite scroll](https://htmx.org/examples/infinite-scroll/)\nexample:\n\n```html\n<tr fx-action=\"/contacts/?page=2\"\n    fx-trigger=\"intersect\"\n    fx-swap=\"afterend\">\n  <td>Agent Smith</td>\n  <td>void29@null.org</td>\n  <td>55F49448C0</td>\n</tr>\n```\n\n### Custom Swapping Algorithms\n\nFor DOM morphing specifically, the fixi-project ships\n[paxi.js](https://github.com/bigskysoftware/paxi), which registers itself as the `morph`\nswap strategy for you:\n\n```html\n<script src=\"fixi.js\"></script>\n<script src=\"paxi.js\"></script>\n\n<button fx-action=\"/demo\" fx-swap=\"morph\" fx-target=\"#output\">Morph New Content</button>\n<output id=\"output\"></output>\n```\n\nMore generally, you can implement any custom swap strategy using the\n[`fx:config`](#fxconfig) event, wiring a function into `evt.detail.cfg.swap`. Here is an\nexample that wires in [Idiomorph](https://github.com/bigskysoftware/idiomorph) for the\n`morph` & `innerMorph` values (paxi does something similar in a smaller package):\n\n```js\n// fixi morphing extension\ndocument.addEventListener(\"fx:config\", (evt) => {\n  function morph(cfg, style) {\n    Idiomorph.morph(cfg.target, cfg.text, { morphStyle: style }).forEach((n) => {\n      // process nodes as morphing existing nodes will not trigger fixi MutationObserver\n      n.dispatchEvent(new CustomEvent(\"fx:process\", { bubbles: true }));\n    });\n  }\n  if (evt.detail.cfg.swap == \"morph\")\n    evt.detail.cfg.swap = (cfg) => morph(cfg, \"outerHTML\");\n  if (evt.detail.cfg.swap == \"innerMorph\")\n    evt.detail.cfg.swap = (cfg) => morph(cfg, \"innerHTML\");\n});\n```\n\n### Implementing Attribute Inheritance\n\nfixi does not implement [attribute inheritance](https://htmx.org/docs/#inheritance) like htmx does, but you can modify \nthe fixi source to do so easily.\n\nSimply change this line:\n```js\n    let attr = (elt, name, defaultVal)=>elt.getAttribute(name) || defaultVal\n```\nto this:\n```js\n    let attr = (elt, name, defaultVal)=>elt.closest(`[${name}]`)?.getAttribute(name) || defaultVal\n```\n\n### Implementing History Support\n\nfixi does not implement history support, but you can add rudimentary support like so:\n\n```js\n    function initJS() {\n      // initialize javascript things here\n    }\n    document.addEventListener(\"DOMContentLoaded\", (evt)=>{\n        initJS();\n    });\n    document.addEventListener(\"fx:after\", (evt)=>{\n        if (evt.target.hasAttribute(\"ext-fx-push\")){\n            history.replaceState({fixi:true, url:location.href}, \"\", location.href)\n            history.pushState({fixi:true, url:evt.detail.cfg.response.url}, \"\", evt.detail.cfg.response.url)\n        }\n    })\n    window.addEventListener(\"popstate\", async(evt)=>{\n        if (evt.state.fixi){\n            let historyResp = await fetch(evt.state.url)\n            document.documentElement.innerHTML = await historyResp.text()\n            document.dispatchEvent(new CustomEvent(\"fx:process\"))\n            initJS()\n        }\n    })\n```\n\nThis adds an event listener for the `fx:after` event, and if the element has the `ext-fx-push` attribute it uses the\n[JavaScript History API](https://developer.mozilla.org/en-US/docs/Web/API/History_API) to update the URL in the browser.\n\nNote that, like with htmx, this mechanism expects that remote systems will return full pages when the given URL is\nrequested, so that things like refresh will work properly.  Note also that scripts in head tags that are swapped in\nby fixi are _not_ executed by default, which is the browser standard for whatever reason.\n\nWith this extension, you can write code like this:\n\n```html\n<a href=\"/example.html\"\n   fx-action=\"/example.html\"\n   fx-target=\"html\"\n   ext-fx-push>\n    Example Fixi-Powered Link\n</a>\n```\n\nAnd fixi will handle the click on this link, and the URL of the site will properly update, assuming JavaScript is \nenabled.\n\nMore sophisticated History handling (in particular, `head` tag handling) is left as an exercise for the reader.\n\n## LICENCE\n\n```\nZero-Clause BSD\n=============\n\nPermission to use, copy, modify, and/or distribute this software for\nany purpose with or without fee is hereby granted.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL\nWARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES\nOF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE\nFOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY\nDAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN\nAN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT\nOF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n```\n\n<h2>&#x1f480; <i>Memento Mori</i></h2>\n\n```js\n/**\n* Adding a single line to this file requires great internal reflection\n* and thought.  You must ask yourself if your one line addition is so\n* important, so critical to the success of the company, that it warrants\n* a slowdown for every user on every page load.  Adding a single letter\n* here could cost thousands of man hours around the world.\n* \n* That is all.\n*/\n```\n\n-- [A comment](https://www.youtube.com/watch?v=wHlyLEPtL9o&t=1528s) at the beginning of [Primer](https://gist.github.com/makinde/376039)\n\n","readmeFilename":"README.md"}