@barefootjs/router 0.14.0

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.
package/README.md ADDED
@@ -0,0 +1,73 @@
1
+ # @barefootjs/router
2
+
3
+ Backend-agnostic, progressively-enhanced client router for BarefootJS. It
4
+ intercepts same-origin navigations, fetches the **ordinary full-page HTML** any
5
+ backend already returns (no protocol header, no JSON endpoint), swaps only the
6
+ page **region**, and disposes/re-hydrates just the islands inside it. The shell
7
+ stays mounted; everything outside the region keeps its DOM, scroll, and state.
8
+
9
+ ```ts
10
+ import { startRouter } from '@barefootjs/router'
11
+
12
+ startRouter() // install once on the client
13
+ ```
14
+
15
+ Mark the swappable region in your layout with `<Region>` (compiled to
16
+ `[bf-region]`, see `spec/router.md`); the router swaps the first match.
17
+
18
+ ## Public API
19
+
20
+ - `startRouter(options?): Router` — install once; SSR no-op. Returns
21
+ `{ stop, navigate, prefetch }`.
22
+ - `navigate(href, { history? }): Promise<void>` — programmatic navigation.
23
+ - `BF_REGION` — re-export of the `bf-region` marker for server-side helpers.
24
+
25
+ ### Options
26
+
27
+ | option | default | purpose |
28
+ | --- | --- | --- |
29
+ | `region` | `[bf-region]` | selector for the swap point |
30
+ | `rehydrate` / `dispose` | runtime fallback chain | override island lifecycle |
31
+ | `loadModule` | `import(src)` | how island modules are loaded |
32
+ | `shouldIntercept` | same-origin, plain click | per-anchor opt-out (`data-bf-router="false"`, `download`, `target`, `rel=external`) |
33
+ | `prefetch` / `prefetchDelay` | `true` / `65` | hover/focus/pointerdown prefetch + `modulepreload` |
34
+ | `cacheFreshMs` / `cacheStaleMs` / `cacheCap` | `15000` / `60000` / `30` | SWR + LRU snapshot cache |
35
+ | `scrollToTop` | `true` | scroll to top after a swap |
36
+ | `manageFocus` | `true` | move focus into the region + announce the route |
37
+ | `morph` | `true` | preserve `[data-bf-permanent]` live nodes across a swap (no-op when none present); `false` forces a plain `replaceChildren` |
38
+
39
+ ## Correct by default
40
+
41
+ The client runtime is an **optional peer**: a fully static shell ships the
42
+ router with zero `@barefootjs/client`. When islands are present, `dispose` and
43
+ `rehydrate` degrade through the same fallback chain
44
+ (`window.__bf_*` seams → `@barefootjs/client/runtime`'s
45
+ `disposeScope`/`rehydrateScope`) and never silently no-op. There is no opt-in
46
+ setup step.
47
+
48
+ ## Behaviour
49
+
50
+ - **Last-wins** across overlapping navigations (a newer nav aborts the older).
51
+ - **SWR cache** stores promises (a prefetch + click share one request); aging
52
+ entries serve instantly and refresh in the background; failures aren't cached.
53
+ - **Module-aware**: a response's new island modules are imported *before* the
54
+ re-hydration walk, and deduped across navigations.
55
+ - **Redirect-aware**: history commits at the response's final URL.
56
+ - **History.state preserved**: a router replace merges rather than clobbers
57
+ existing state (scroll-restoration libs, framework state).
58
+ - **A11y**: focus moves into the swapped region (its first heading) and the new
59
+ title is announced via a polite live region.
60
+ - **Persistence** (`data-bf-permanent`): an element marked
61
+ `<div data-bf-permanent="player">` keeps its *live* node across a swap — its
62
+ state, media playback, scroll, and hydrated scope survive — matched between
63
+ documents by the attribute value (or `id`). A no-op when no element is marked;
64
+ pass `morph: false` for a plain swap.
65
+
66
+ ## Scope
67
+
68
+ A **single authored region** (the broadest `[bf-region]` match), correct by
69
+ default. `searchParams()` (query-only navigation without a swap) ships in
70
+ `@barefootjs/client` (v0.5); the router drives it via the `__bf_pushSearch`
71
+ seam, so query-only navigations short-circuit once a consumer is present.
72
+ `data-bf-permanent` persistence is v1. Compiler-derived **nested** regions
73
+ (deepest-differs swap, sibling master–detail) are v2.
package/dist/a11y.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Focus management and route-change announcement on swap (spec/router.md
3
+ * lifecycle step 7).
4
+ *
5
+ * A client swap replaces content without a browser navigation, so the browser
6
+ * does none of the accessibility work a real page load does: focus is left on a
7
+ * now-detached node and a screen reader never hears that the route changed.
8
+ * After a region swap the router (a) moves focus into the new region and (b)
9
+ * announces the route via a polite live region, mirroring what Remix/Turbo do.
10
+ */
11
+ /**
12
+ * Move focus into the freshly swapped region so keyboard/AT users resume there
13
+ * rather than at the top of a detached tree. Prefer the region's first heading
14
+ * (gives a screen reader the page context); fall back to the region element
15
+ * itself, made programmatically focusable without becoming a tab stop.
16
+ */
17
+ export declare function focusRegion(region: Element): void;
18
+ /**
19
+ * Announce the new route through a singleton `aria-live="polite"` region kept
20
+ * visually hidden. Setting its text after the swap lets a screen reader read
21
+ * the new page title without stealing focus.
22
+ */
23
+ export declare function announceNavigation(title: string | null): void;
24
+ //# sourceMappingURL=a11y.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"a11y.d.ts","sourceRoot":"","sources":["../src/a11y.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAejD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAS7D"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Stale-while-revalidate snapshot cache with an LRU bound.
3
+ *
4
+ * The cache stores *Promises*, so a prefetch and a near-simultaneous click for
5
+ * the same URL share a single in-flight request (single-flight). Three states,
6
+ * keyed on `now` vs the entry's `refreshAt`/`staleAt`:
7
+ * - fresh (now < refreshAt) → serve cached, no refetch.
8
+ * - aging (refreshAt ≤ now < staleAt) → serve cached AND refresh in the
9
+ * background (single-flight), so a swap is instant while the next is fresh.
10
+ * - stale (now ≥ staleAt) / miss → fetch synchronously; never serve stale.
11
+ */
12
+ import type { PageSnapshot, RouterState } from './types.ts';
13
+ export declare function fetchSnapshot(state: RouterState, url: string): Promise<PageSnapshot | null>;
14
+ export declare function loadPage(state: RouterState, url: string): Promise<PageSnapshot | null>;
15
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAI3D,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAa3F;AAuBD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAwBtF"}
@@ -0,0 +1,4 @@
1
+ export { startRouter, navigate } from './router.ts';
2
+ export type { RouterOptions, NavigateOptions, Router } from './types.ts';
3
+ export { BF_REGION } from '@barefootjs/shared';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACnD,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAGxE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA"}