@weft-js/start 0.1.4 → 0.1.5-nightly.20261111.3
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/package.json +2 -2
- package/src/boot.ts +2 -0
- package/src/capture.ts +3 -1
- package/src/run/admission.ts +10 -23
- package/src/run/listeners.ts +12 -1
- package/src/run/navigation.ts +4 -1
- package/src/run/prefetch.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weft-js/start",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5-nightly.20261111.3",
|
|
4
4
|
"description": "weft Start: file routes, server functions, streaming SSR, a database and auth",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"weft"
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"vite": "8.3.5"
|
|
79
79
|
},
|
|
80
80
|
"peerDependencies": {
|
|
81
|
-
"@weft-js/core": "^0.1.
|
|
81
|
+
"@weft-js/core": "^0.1.5-nightly.20261111.3",
|
|
82
82
|
"better-auth": "^1.7.0",
|
|
83
83
|
"drizzle-orm": "^0.45.0",
|
|
84
84
|
"drizzle-zod": "^0.8.0",
|
package/src/boot.ts
CHANGED
package/src/capture.ts
CHANGED
|
@@ -6,10 +6,12 @@
|
|
|
6
6
|
//
|
|
7
7
|
// q the events n the page needs its code at once w wake: (element, lead), a lead being only a hint of intent
|
|
8
8
|
// p replay one r replaying, so don't capture d the visitor asked for less data l the last lead
|
|
9
|
+
// s the page is running: until then a page that hydrates whole (`n`) holds a click, submit or reset anywhere, not just
|
|
10
|
+
// on an island, since nothing is listening yet. A plain link is left to the browser: it works without code.
|
|
9
11
|
//
|
|
10
12
|
// Written by hand and small, since every such page pays it: `boot.ts` and weft's `island.ts` are the other two halves.
|
|
11
13
|
|
|
12
|
-
const CODE = `(n=>{var d=document,c=navigator.connection,q=window.__wq={q:[],n:n,w(){},d:matchMedia("(prefers-reduced-data: reduce)").matches||!!(c&&c.saveData),p(e){var t=e.target,r=q.r,s=e.submitter;if(t.isConnected){q.r=1;try{e.type=="submit"?t.requestSubmit(s&&s.isConnected?s:void 0):e.type=="reset"?t.reset():t.dispatchEvent(new e.constructor(e.type,e))}finally{q.r=r}}}},o=t=>t.closest&&t.closest("[wk]"),h=e=>{var k=!q.r&&o(e.
|
|
14
|
+
const CODE = `(n=>{var d=document,c=navigator.connection,q=window.__wq={q:[],n:n,w(){},d:matchMedia("(prefers-reduced-data: reduce)").matches||!!(c&&c.saveData),p(e){var t=e.target,r=q.r,s=e.submitter;if(t.isConnected){q.r=1;try{e.type=="submit"?t.requestSubmit(s&&s.isConnected?s:void 0):e.type=="reset"?t.reset():t.dispatchEvent(new e.constructor(e.type,e))}finally{q.r=r}}}},o=t=>t.closest&&t.closest("[wk]"),h=e=>{var t=e.target,k=!q.r&&(o(t)||q.n&&!q.s&&/^(click|submit|reset)$/.test(e.type)&&t.closest&&!t.closest("a[href]")&&d.documentElement);if(k&&(!k.$k||q.q.length)&&!((e.metaKey||e.ctrlKey||e.shiftKey)&&e.type=="click"&&e.target.closest("a"))){q.q.push(e);/^(click|submit|reset)$/.test(e.type)&&e.preventDefault();q.w(k)}},l=e=>{var k=o(e.target);k&&!k.$k&&!q.d&&q.w(k,1)};"click dblclick pointerdown pointerup mousedown mouseup keydown keyup input change submit reset focus blur focusin focusout".split(" ").forEach(t=>d.addEventListener(t,h,!0));"pointerover touchstart".split(" ").forEach(t=>d.addEventListener(t,l,{capture:!0,passive:!0}))})`
|
|
13
15
|
|
|
14
16
|
/** The script's text. `now`: the page has work that can't wait, so its code loads straight away. */
|
|
15
17
|
export const capture = (now: boolean) => `${CODE}(${now ? 1 : 0})`
|
package/src/run/admission.ts
CHANGED
|
@@ -35,46 +35,33 @@ export function admitLink(event: Event, routes: ClientRoutes) {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Links go on their click. A press (pointerdown) only warms the route: it starts the fetch the click will claim, so the
|
|
40
|
+
* head start is kept, but the page never changes under a pointer that is still down. Swapping it on the press lost the
|
|
41
|
+
* click itself (the button came up on the new page, so the click landed on <html> or never fired), and with it every
|
|
42
|
+
* click handler: a menu that closes when its link is followed, a sheet, an analytics hook. The returned disposer
|
|
43
|
+
* removes only these listeners.
|
|
44
|
+
*/
|
|
39
45
|
export function routeClicks(
|
|
40
46
|
doc: Document,
|
|
41
47
|
routes: ClientRoutes,
|
|
42
48
|
go: (href: string) => void | Promise<void>,
|
|
49
|
+
warm?: (url: URL) => void,
|
|
43
50
|
) {
|
|
44
|
-
let accepted: { id: number; anchor: HTMLAnchorElement } | undefined
|
|
45
51
|
const down = (event: PointerEvent) => {
|
|
46
|
-
accepted = undefined
|
|
47
52
|
const target = admitLink(event, routes)
|
|
48
|
-
if (!target)
|
|
49
|
-
accepted = { id: event.pointerId, anchor: target.anchor }
|
|
50
|
-
if (!target.noop) void Promise.resolve(go(target.url.href)).catch(() => {})
|
|
51
|
-
}
|
|
52
|
-
const cancel = (event: PointerEvent) => {
|
|
53
|
-
if (accepted?.id === event.pointerId) accepted = undefined
|
|
53
|
+
if (target && !target.noop) warm?.(target.url)
|
|
54
54
|
}
|
|
55
55
|
const click = (event: MouseEvent) => {
|
|
56
|
-
const record = accepted
|
|
57
|
-
accepted = undefined
|
|
58
|
-
const pointer = event as PointerEvent
|
|
59
|
-
if (
|
|
60
|
-
record &&
|
|
61
|
-
event.detail > 0 &&
|
|
62
|
-
(pointer.pointerId === record.id || event.composedPath().includes(record.anchor))
|
|
63
|
-
) {
|
|
64
|
-
event.preventDefault()
|
|
65
|
-
return
|
|
66
|
-
}
|
|
67
56
|
const target = admitLink(event, routes)
|
|
68
57
|
if (!target) return
|
|
69
58
|
event.preventDefault()
|
|
70
|
-
if (!target.noop) void go(target.url.href)
|
|
59
|
+
if (!target.noop) void Promise.resolve(go(target.url.href)).catch(() => {})
|
|
71
60
|
}
|
|
72
61
|
doc.addEventListener('pointerdown', down)
|
|
73
|
-
doc.addEventListener('pointercancel', cancel)
|
|
74
62
|
doc.addEventListener('click', click)
|
|
75
63
|
return () => {
|
|
76
64
|
doc.removeEventListener('pointerdown', down)
|
|
77
|
-
doc.removeEventListener('pointercancel', cancel)
|
|
78
65
|
doc.removeEventListener('click', click)
|
|
79
66
|
}
|
|
80
67
|
}
|
package/src/run/listeners.ts
CHANGED
|
@@ -30,6 +30,7 @@ export function listeners(nav: Navigation, hot: () => Hot | undefined) {
|
|
|
30
30
|
const q = (window as Window & { __wq?: Held }).__wq
|
|
31
31
|
if (q) {
|
|
32
32
|
q.w = wake
|
|
33
|
+
q.s = 1
|
|
33
34
|
if (q.l) wake(q.l)
|
|
34
35
|
drain()
|
|
35
36
|
}
|
|
@@ -37,7 +38,17 @@ export function listeners(nav: Navigation, hot: () => Hot | undefined) {
|
|
|
37
38
|
|
|
38
39
|
// A fade can intercept a click; replay it on the element underneath once the picture is gone.
|
|
39
40
|
function clicks(nav: Navigation) {
|
|
40
|
-
|
|
41
|
+
const network = (
|
|
42
|
+
navigator as Navigator & { connection?: { saveData?: boolean; effectiveType?: string } }
|
|
43
|
+
).connection
|
|
44
|
+
routeClicks(
|
|
45
|
+
document,
|
|
46
|
+
nav.routes,
|
|
47
|
+
(href) => nav.go(href),
|
|
48
|
+
(url) => {
|
|
49
|
+
if (!document.hidden) nav.prefetch.start(url, 'press', network, navigator.onLine)
|
|
50
|
+
},
|
|
51
|
+
)
|
|
41
52
|
let ended: Promise<unknown> | undefined
|
|
42
53
|
document.addEventListener(
|
|
43
54
|
'pointerdown',
|
package/src/run/navigation.ts
CHANGED
|
@@ -123,7 +123,10 @@ export class Navigation {
|
|
|
123
123
|
if (!current()) return
|
|
124
124
|
if (scroll) {
|
|
125
125
|
const target = url.hash && document.getElementById(decodeURIComponent(url.hash.slice(1)))
|
|
126
|
-
|
|
126
|
+
// a new page arrives where it starts, at once: warp makes the root scroll smooth for in-page anchors, and a
|
|
127
|
+
// route change that glided down from the old page's offset read as the old page scrolling away
|
|
128
|
+
if (target) target.scrollIntoView({ behavior: 'instant' })
|
|
129
|
+
else window.scrollTo({ top: 0, left: 0, behavior: 'instant' })
|
|
127
130
|
}
|
|
128
131
|
}
|
|
129
132
|
}
|
package/src/run/prefetch.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { RouteCache, Read } from './cache'
|
|
2
2
|
import { ask } from './helpers'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/** hover is a guess; focus and a press (the button is down, the click is coming) are near certain */
|
|
5
|
+
type Intent = 'hover' | 'focus' | 'press'
|
|
5
6
|
type Network = { saveData?: boolean; effectiveType?: string }
|
|
6
7
|
type Job = { ctrl: AbortController; promise: Promise<Read | undefined>; promoted: boolean }
|
|
7
8
|
|