Startr Swap

Startr Swap swaps links and forms in place, with no build step. Drop it in, mark a region, and same-origin links stop reloading the page: they swap in place instead, wrapped in a CSS View Transition.

It fetches the page and parses it with fetch + DOMParser. There is no hidden iframe. An iframe performs a REAL navigation, so scripts in the response execute inside it, in the wrong document, silently, and every swap re-requests the page's stylesheets. fetch + DOMParser parses inert, and reports a redirect honestly.

Lineage is htmz (MIT, © Lean Rada), whose hash-names-the-target convention it keeps.

Install

<script defer src="https://startr.style/v1.1/swap.js"
        integrity="sha384-/r9AsVAT1hdM4Gnh+mBr87H2TByKjMe9SG2H8RYYvaD5rSY90Q9afydmOHxtGX6V"
        crossorigin="anonymous"></script>
<main data-swap> ... </main>

/v1.1/swap.js is immutable and cached for a year. /swap.js is the rolling build this site uses itself and carries no integrity guarantee. The sha256 is published at /v1.1/swap.js.sha256.

/v1/swap.js stays exactly as it shipped, so its integrity hash keeps matching, and it keeps the old timing: under a View Transition swap:after fires before the new content is in. A listener that reads the swapped content wants /v1.1/ or later. Version 1.1 also adds a License: MIT line to the header and makes the comments ASCII-only, which is why every pin changes.

Markup

The API is three attributes and four events, and that is the whole thing.

Attribute Placed on Meaning
data-swap a region Marks it swappable. A value narrows which links are taken over to a path prefix, and nested regions inherit it.
data-swap-target a control Updates THAT region, by selector, instead of the one this control lives in.
data-swap-off a control Never swaps this one.

Only the region swaps; everything outside it stays. A page whose chrome differs outside the region should not declare one, and links into it should carry data-swap-off. This site does exactly that for its home page, whose hero sits outside <main>.

Events

All four fire on the region, bubbling, cancelable.

  • swap:before — cancel to abandon the swap; the browser navigates normally.
  • swap:navigate — cancel to own the address bar yourself.
  • swap:after — content is in. From 1.1 on this holds under a View Transition too: the event fires inside the transition's update callback, after the content, the title and the history entry. /v1/swap.js fires it earlier.
  • swap:error — cancel to suppress the fallback navigation.

Regions nest, so sub-swaps cost no syntax at all. A pager inside a region updates that region and leaves the page alone, declaring nothing. A sub-swap region does need an id, because the response has to be able to name the piece coming back.

Things every consumer hits

  1. There is no <main> default, deliberately. The first version claimed <main> when no region was declared. Any document can end up running this file — a host application that adopts a fetched page's scripts adopts this one too — and a <main> default would then take over every link in a document that never asked for it. A region must be declared.
  2. The click listener CAPTURES; the submit listener does not. A single-page router binds its own document-level click listener at boot, so in the bubble phase it always wins. Capture puts a declared region ahead of a generic router. Submit stays in the bubble phase on purpose, because attribute-driven libraries bind submits on the FORM, and capturing would take their submits away from them.
  3. A POST that redirected must push history; a POST that answered in place must not. Post/Redirect/Get lands on a plain address a reader can reload and share. A POST that answered in place has an address that only accepts POST, so pushing it hands the reader a reload that 405s. res.redirected is the exact signal.
  4. An inline <script> in a swapped region never runs. The library re-runs only script[src]. Re-create inline scripts from the host, as this site does in src/_includes/swap-host.njk with a MutationObserver.
  5. On /v1/swap.js, under a View Transition, swap:after fires before the update callback has run, so it sees the old content. Fixed in /v1.1/swap.js. If you must stay on /v1/, observe the region with a MutationObserver instead.

Size

13,748 bytes raw, 5,436 gzipped (as the gate measures it). htmx is 50,917 raw and 16,367 gzipped. The gate scripts/gates/startr-swap/check.py holds a ceiling of 8,192 gzipped.

Demo

/demo/swap/a.html and b.html swap between each other.

/demo/swap/none.html loads the script, declares no region, keeps a <main> on purpose, and must navigate normally.

Licence

MIT, the same as the framework. Source: https://github.com/Startr/Style.

Star Fork Download

Carefully crafted by the Startr Team with the help of our contributors.

Startr Style licensed MIT, docs CC BY 3.0.