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.jsfires 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
- 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. - 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.
- 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.redirectedis the exact signal. - An inline
<script>in a swapped region never runs. The library re-runs onlyscript[src]. Re-create inline scripts from the host, as this site does insrc/_includes/swap-host.njkwith a MutationObserver. - On
/v1/swap.js, under a View Transition,swap:afterfires 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.