@pylonsync/react 0.4.11 → 0.4.12
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/dist/Link.d.ts +2 -1
- package/package.json +3 -3
- package/src/Link.tsx +13 -4
- package/src/useRouter.test.ts +12 -3
package/dist/Link.d.ts
CHANGED
|
@@ -3,7 +3,8 @@ declare global {
|
|
|
3
3
|
interface Window {
|
|
4
4
|
__pylon?: {
|
|
5
5
|
prefetch: (href: string, opts?: {
|
|
6
|
-
/** Also fetch the page payload
|
|
6
|
+
/** Also fetch the page payload. Costs a server render, so only on an
|
|
7
|
+
* intent signal: hover, touch, or focus. */
|
|
7
8
|
document?: boolean;
|
|
8
9
|
}) => Promise<void>;
|
|
9
10
|
navigate: (href: string, opts?: {
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.4.
|
|
6
|
+
"version": "0.4.12",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "./src/index.ts",
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"prepack": "bun run build"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@pylonsync/sdk": "0.4.
|
|
18
|
-
"@pylonsync/sync": "0.4.
|
|
17
|
+
"@pylonsync/sdk": "0.4.12",
|
|
18
|
+
"@pylonsync/sync": "0.4.12"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
21
|
"react": ">=19.0.0"
|
package/src/Link.tsx
CHANGED
|
@@ -11,9 +11,11 @@
|
|
|
11
11
|
// - Prefetches in two stages, because the two halves cost very
|
|
12
12
|
// different things. On viewport entry (IntersectionObserver) the
|
|
13
13
|
// runtime modulepreloads the destination's client chunks — static,
|
|
14
|
-
// immutable, cacheable. On hover
|
|
15
|
-
// itself, which costs a server render, so that waits for a
|
|
16
|
-
// signal of intent rather than firing for a screenful of links.
|
|
14
|
+
// immutable, cacheable. On hover, touch, or focus it also fetches
|
|
15
|
+
// the page itself, which costs a server render, so that waits for a
|
|
16
|
+
// real signal of intent rather than firing for a screenful of links.
|
|
17
|
+
// Focus is included so a keyboard user tabbing to a link gets the
|
|
18
|
+
// same warm path a mouse user gets.
|
|
17
19
|
// - Modifier keys (cmd/ctrl/shift/alt) or middle-click fall
|
|
18
20
|
// through to the browser's default behavior (open in new tab,
|
|
19
21
|
// etc.) — matches Next.js semantics.
|
|
@@ -32,7 +34,8 @@ declare global {
|
|
|
32
34
|
prefetch: (
|
|
33
35
|
href: string,
|
|
34
36
|
opts?: {
|
|
35
|
-
/** Also fetch the page payload
|
|
37
|
+
/** Also fetch the page payload. Costs a server render, so only on an
|
|
38
|
+
* intent signal: hover, touch, or focus. */
|
|
36
39
|
document?: boolean;
|
|
37
40
|
},
|
|
38
41
|
) => Promise<void>;
|
|
@@ -153,11 +156,17 @@ export function Link({
|
|
|
153
156
|
|
|
154
157
|
el.addEventListener("mouseenter", warmPage);
|
|
155
158
|
el.addEventListener("touchstart", warmPage, { passive: true });
|
|
159
|
+
// Focus is the keyboard's version of hover, and a stronger signal: a
|
|
160
|
+
// cursor slides across links it isn't aiming at, but nobody tabs onto one
|
|
161
|
+
// idly. Without it, tabbing to a link and pressing Enter took the cold
|
|
162
|
+
// path while a mouse user got the warm one — the wrong way round.
|
|
163
|
+
el.addEventListener("focus", warmPage);
|
|
156
164
|
|
|
157
165
|
return () => {
|
|
158
166
|
io?.disconnect();
|
|
159
167
|
el.removeEventListener("mouseenter", warmPage);
|
|
160
168
|
el.removeEventListener("touchstart", warmPage);
|
|
169
|
+
el.removeEventListener("focus", warmPage);
|
|
161
170
|
};
|
|
162
171
|
}, [href, prefetch]);
|
|
163
172
|
|
package/src/useRouter.test.ts
CHANGED
|
@@ -48,10 +48,16 @@ describe("notFound() — branded for the SSR not-found boundary", () => {
|
|
|
48
48
|
describe("redirect() — client-side replace navigation", () => {
|
|
49
49
|
let calls: Array<{ href: string; opts?: unknown }>;
|
|
50
50
|
const hadWindow = "window" in globalThis;
|
|
51
|
+
// The suite preloads happy-dom, so a real window usually EXISTS here. Put it
|
|
52
|
+
// back rather than leaving the stub in place: bun isolates globals per test
|
|
53
|
+
// file today, so nothing observable depends on this, but a test that
|
|
54
|
+
// replaces a global and never restores it is one isolation change away from
|
|
55
|
+
// breaking every file that mounts.
|
|
56
|
+
const originalWindow = (globalThis as any).window;
|
|
51
57
|
|
|
52
58
|
beforeEach(() => {
|
|
53
59
|
calls = [];
|
|
54
|
-
//
|
|
60
|
+
// Stand up a minimal window the module can see, without assuming a DOM.
|
|
55
61
|
(globalThis as any).window = {
|
|
56
62
|
__pylon: {
|
|
57
63
|
prefetch: async () => {},
|
|
@@ -63,8 +69,11 @@ describe("redirect() — client-side replace navigation", () => {
|
|
|
63
69
|
});
|
|
64
70
|
|
|
65
71
|
afterEach(() => {
|
|
66
|
-
if (hadWindow)
|
|
67
|
-
|
|
72
|
+
if (hadWindow) {
|
|
73
|
+
(globalThis as any).window = originalWindow;
|
|
74
|
+
} else {
|
|
75
|
+
delete (globalThis as any).window;
|
|
76
|
+
}
|
|
68
77
|
});
|
|
69
78
|
|
|
70
79
|
test("delegates to __pylon.navigate with replace:true (no history push)", () => {
|