@ogygia/core 0.12.2 → 0.13.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/dist/LateIsland.svelte +3 -2
- package/dist/LateIslandAwait.svelte +6 -2
- package/dist/Region.svelte +22 -33
- package/dist/boundary.d.ts +0 -2
- package/dist/boundary.js +8 -6
- package/dist/compiler/debarrel/barrel.d.ts +15 -8
- package/dist/compiler/debarrel/barrel.js +92 -45
- package/dist/compiler/debarrel/plugin.js +41 -17
- package/dist/compiler/dev/dev-hmr.d.ts +3 -0
- package/dist/compiler/dev/dev-hmr.js +3 -0
- package/dist/compiler/driver.js +11 -4
- package/dist/compiler/flags.js +21 -7
- package/dist/compiler/fouc-css.js +5 -1
- package/dist/compiler/kit.js +5 -1
- package/dist/compiler/link/build-output.js +12 -3
- package/dist/compiler/macros/bake.js +18 -4
- package/dist/compiler/macros/dollar.js +6 -1
- package/dist/compiler/macros/source.js +6 -1
- package/dist/compiler/macros/store.js +10 -2
- package/dist/compiler/macros/wire.js +6 -1
- package/dist/compiler/parse/scan.js +10 -3
- package/dist/compiler/region/transform.js +188 -74
- package/dist/context-bridge.js +13 -5
- package/dist/context.d.ts +9 -0
- package/dist/context.js +30 -1
- package/dist/document.d.ts +1 -1
- package/dist/document.js +7 -1
- package/dist/early-holes-head.d.ts +5 -0
- package/dist/early-holes-head.js +20 -0
- package/dist/early-holes.d.ts +34 -0
- package/dist/early-holes.js +51 -0
- package/dist/entry-hydrate.js +9 -12
- package/dist/events/bus.js +7 -2
- package/dist/events/schema.d.ts +2 -2
- package/dist/fn-transport.js +4 -3
- package/dist/hooks.js +94 -163
- package/dist/insights/page-assets.js +11 -3
- package/dist/insights/page-insights.d.ts +0 -2
- package/dist/insights/page-insights.js +14 -10
- package/dist/insights/session-insights.js +2 -2
- package/dist/internal-instruments.d.ts +3 -1
- package/dist/internal-instruments.js +3 -1
- package/dist/internal-server.d.ts +3 -2
- package/dist/internal-server.js +3 -2
- package/dist/internal.d.ts +1 -2
- package/dist/internal.js +1 -2
- package/dist/keep-fallback.d.ts +0 -2
- package/dist/keep-fallback.js +4 -5
- package/dist/live-transport.js +3 -2
- package/dist/messages/build.d.ts +585 -0
- package/dist/messages/build.js +800 -0
- package/dist/messages/core.d.ts +464 -0
- package/dist/messages/core.js +843 -0
- package/dist/messages/define.d.ts +62 -0
- package/dist/messages/define.js +49 -0
- package/dist/messages/index.d.ts +6 -0
- package/dist/messages/index.js +6 -0
- package/dist/messages/island.d.ts +4 -0
- package/dist/messages/island.js +34 -0
- package/dist/messages/lazy.d.ts +3 -0
- package/dist/messages/lazy.js +22 -0
- package/dist/messages/prose.d.ts +15 -0
- package/dist/messages/prose.js +43 -0
- package/dist/messages/server.d.ts +142 -0
- package/dist/messages/server.js +246 -0
- package/dist/nested.d.ts +3 -0
- package/dist/nested.js +8 -1
- package/dist/plugin/css.js +14 -13
- package/dist/plugin/facts.d.ts +2 -0
- package/dist/plugin/facts.js +10 -6
- package/dist/plugin/hooks.d.ts +7 -17
- package/dist/plugin/hooks.js +32 -4
- package/dist/plugin/index.d.ts +9 -2
- package/dist/plugin/request-scope.d.ts +20 -0
- package/dist/plugin/request-scope.js +149 -0
- package/dist/plugin/server.d.ts +20 -2
- package/dist/plugin/server.js +98 -28
- package/dist/plugin/testing.d.ts +6 -0
- package/dist/plugin/testing.js +14 -2
- package/dist/policies.d.ts +21 -0
- package/dist/policies.js +66 -0
- package/dist/preference.js +6 -1
- package/dist/preload.js +6 -9
- package/dist/ref-watch.d.ts +0 -10
- package/dist/ref-watch.js +14 -1
- package/dist/ref.js +17 -5
- package/dist/region-snippet.js +9 -3
- package/dist/region.js +2 -1
- package/dist/report-thrown.d.ts +9 -0
- package/dist/report-thrown.js +15 -0
- package/dist/rules.d.ts +17 -0
- package/dist/rules.js +57 -0
- package/dist/runtime/beacon.d.ts +0 -4
- package/dist/runtime/beacon.js +58 -43
- package/dist/runtime/boot-link.js +19 -2
- package/dist/runtime/client-hooks.js +2 -1
- package/dist/runtime/concurrency.d.ts +16 -5
- package/dist/runtime/concurrency.js +123 -7
- package/dist/runtime/core.d.ts +3 -1
- package/dist/runtime/core.js +718 -384
- package/dist/runtime/devalue-lane.d.ts +13 -0
- package/dist/runtime/devalue-lane.js +49 -0
- package/dist/runtime/disclose-version.d.ts +1 -0
- package/dist/runtime/disclose-version.js +14 -0
- package/dist/runtime/early-holes.d.ts +4 -0
- package/dist/runtime/early-holes.js +40 -0
- package/dist/runtime/entry-locations.js +40 -49
- package/dist/runtime/foreign-props.d.ts +0 -7
- package/dist/runtime/foreign-props.js +2 -1
- package/dist/runtime/form-continuity.d.ts +2 -2
- package/dist/runtime/form-continuity.js +30 -8
- package/dist/runtime/frame-nav.d.ts +7 -1
- package/dist/runtime/frame-nav.js +84 -18
- package/dist/runtime/frame-store.d.ts +25 -16
- package/dist/runtime/frame-store.js +124 -98
- package/dist/runtime/frames.js +8 -4
- package/dist/runtime/hole-facts.js +6 -7
- package/dist/runtime/hole-fetch.d.ts +17 -0
- package/dist/runtime/hole-fetch.js +47 -0
- package/dist/runtime/hydrate-core.d.ts +4 -4
- package/dist/runtime/hydrate-core.js +80 -64
- package/dist/runtime/import-again.d.ts +33 -0
- package/dist/runtime/import-again.js +217 -0
- package/dist/runtime/interaction-replay.d.ts +5 -3
- package/dist/runtime/interaction-replay.js +6 -6
- package/dist/runtime/interaction.d.ts +14 -3
- package/dist/runtime/interaction.js +46 -23
- package/dist/runtime/kit-page-thread.svelte.d.ts +2 -1
- package/dist/runtime/kit-page-thread.svelte.js +44 -20
- package/dist/runtime/lakes.d.ts +1 -1
- package/dist/runtime/lakes.js +18 -5
- package/dist/runtime/live.d.ts +1 -2
- package/dist/runtime/live.js +6 -8
- package/dist/runtime/load-scheduler.d.ts +29 -18
- package/dist/runtime/load-scheduler.js +236 -136
- package/dist/runtime/nav-handle.js +25 -6
- package/dist/runtime/navigation.d.ts +24 -0
- package/dist/runtime/navigation.js +24 -0
- package/dist/runtime/ownership.d.ts +1 -1
- package/dist/runtime/page-defer.js +3 -3
- package/dist/runtime/parse-html.d.ts +15 -4
- package/dist/runtime/parse-html.js +24 -5
- package/dist/runtime/pushable-props.svelte.d.ts +21 -0
- package/dist/runtime/pushable-props.svelte.js +37 -0
- package/dist/runtime/region-endpoint-url.js +4 -6
- package/dist/runtime/region-machines.d.ts +111 -0
- package/dist/runtime/region-machines.js +97 -0
- package/dist/runtime/region-sheets.d.ts +31 -0
- package/dist/runtime/region-sheets.js +49 -0
- package/dist/runtime/restore-report.d.ts +1 -0
- package/dist/runtime/restore-report.js +36 -0
- package/dist/runtime/restore.d.ts +5 -2
- package/dist/runtime/restore.js +33 -14
- package/dist/runtime/router-nav.d.ts +32 -12
- package/dist/runtime/router-nav.js +320 -228
- package/dist/runtime/router.d.ts +83 -19
- package/dist/runtime/router.js +297 -129
- package/dist/runtime/rules.d.ts +35 -0
- package/dist/runtime/rules.js +137 -0
- package/dist/runtime/schedule.d.ts +6 -3
- package/dist/runtime/schedule.js +172 -57
- package/dist/runtime/seeds.d.ts +5 -1
- package/dist/runtime/seeds.js +11 -5
- package/dist/runtime/session.d.ts +46 -7
- package/dist/runtime/session.js +78 -10
- package/dist/runtime/slots.d.ts +53 -20
- package/dist/runtime/slots.js +2 -3
- package/dist/runtime/soft-refresh.d.ts +16 -0
- package/dist/runtime/soft-refresh.js +14 -0
- package/dist/runtime/svelte-warnings.d.ts +4 -10
- package/dist/runtime/svelte-warnings.js +7 -4
- package/dist/runtime/wire-classes.js +21 -8
- package/dist/runtime/wire-format.js +6 -3
- package/dist/script.js +8 -7
- package/dist/seed-ref-reviver.d.ts +0 -9
- package/dist/seed-ref-reviver.js +2 -1
- package/dist/seed-refs.d.ts +46 -36
- package/dist/seed-refs.js +131 -92
- package/dist/server/document-tail.d.ts +6 -0
- package/dist/server/document-tail.js +7 -1
- package/dist/server/early-holes-csp.d.ts +14 -0
- package/dist/server/early-holes-csp.js +73 -0
- package/dist/server/page-facts.d.ts +4 -1
- package/dist/server/page-facts.js +4 -1
- package/dist/server/page-seed.d.ts +2 -2
- package/dist/server/page-seed.js +104 -11
- package/dist/server/page-stream.d.ts +14 -1
- package/dist/server/page-stream.js +119 -2
- package/dist/server/personal-response.d.ts +0 -5
- package/dist/server/personal-response.js +6 -2
- package/dist/server/props-wire.js +5 -1
- package/dist/server/region-endpoint.js +10 -2
- package/dist/server/render-cache.d.ts +24 -20
- package/dist/server/render-cache.js +99 -40
- package/dist/server/render-page.js +46 -15
- package/dist/server/request-stats.d.ts +10 -8
- package/dist/server/request-stats.js +20 -12
- package/dist/server/rules.d.ts +11 -0
- package/dist/server/rules.js +64 -0
- package/dist/server/signing-key.js +31 -18
- package/dist/server/stream-regions.d.ts +14 -1
- package/dist/server/stream-regions.js +38 -2
- package/dist/shared-state.js +5 -1
- package/dist/shims/app-navigation.js +3 -2
- package/dist/shims/page-store.svelte.js +16 -2
- package/dist/source.js +7 -2
- package/dist/store-transport.js +12 -5
- package/dist/transport.js +3 -2
- package/dist/vite/index.js +83 -74
- package/dist/vite/kit-dirs.js +7 -1
- package/dist/vite/options.d.ts +2 -1
- package/dist/vite/options.js +11 -20
- package/dist/vite/package-files.js +13 -3
- package/dist/vite/paths.d.ts +2 -0
- package/dist/vite/paths.js +4 -4
- package/dist/vite/plugin-api.d.ts +6 -5
- package/dist/vite/plugin-api.js +42 -14
- package/dist/vite/style-preprocess.js +10 -3
- package/package.json +8 -1
- package/dist/KeepHost.svelte +0 -19
- package/dist/KeepHost.svelte.d.ts +0 -10
- package/dist/LiveHost.svelte +0 -19
- package/dist/LiveHost.svelte.d.ts +0 -10
- package/dist/NestedProvider.svelte +0 -13
- package/dist/NestedProvider.svelte.d.ts +0 -8
- package/dist/runtime/page-cache.d.ts +0 -23
- package/dist/runtime/page-cache.js +0 -74
package/dist/runtime/router.js
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
import { import_again } from "./import-again.js";
|
|
1
2
|
import { kit_hydrates_page } from "./kit-boot.js";
|
|
3
|
+
import { create_navigation } from "./navigation.js";
|
|
2
4
|
import { slots } from "./slots.js";
|
|
3
5
|
import { publish_nav } from "./nav-handle.js";
|
|
4
6
|
import { nearest_attr } from "./nearest-attr.js";
|
|
5
7
|
import { record_server_body } from "./ownership.js";
|
|
8
|
+
import { runtime_session } from "./session.js";
|
|
9
|
+
import { OgygiaError, warn } from "../messages/core.js";
|
|
10
|
+
import { report_thrown } from "../report-thrown.js";
|
|
11
|
+
import { download } from "../policies.js";
|
|
12
|
+
import { watch_router } from "./rules.js";
|
|
13
|
+
import { STALE, flights } from "@ogygia/machine";
|
|
6
14
|
// src/runtime/router.ts
|
|
7
15
|
/**
|
|
8
16
|
* Minimal SPA router for ogygia (Astro's ClientRouter equivalent).
|
|
@@ -17,6 +25,7 @@ import { record_server_body } from "./ownership.js";
|
|
|
17
25
|
* merge, body reconcile, seeds, a11y) is ./router-nav.ts, loaded on the first prefetch or
|
|
18
26
|
* intercepted click. A visitor who never leaves the page never downloads it.
|
|
19
27
|
*/
|
|
28
|
+
const RULES = import.meta.env?.DEV || (typeof __OGYGIA_RULES__ !== "undefined" ? __OGYGIA_RULES__ : false);
|
|
20
29
|
const WS = /\s+/;
|
|
21
30
|
/**
|
|
22
31
|
* The `<a>` a pointer event is about — through SHADOW ROOTS. A click inside a web component's
|
|
@@ -133,48 +142,159 @@ function jump_to_hash(hash) {
|
|
|
133
142
|
html_el.style.scrollBehavior = prev;
|
|
134
143
|
}
|
|
135
144
|
}
|
|
136
|
-
/**
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
145
|
+
/**
|
|
146
|
+
* THE ROUTER'S HISTORY, one record (#310). The router keeps the visitor's entries it made, each with
|
|
147
|
+
* its address and where its page was left, and reads the rest from it: the page on screen
|
|
148
|
+
* (`current_url`, its document key), the scroll a back or forward step puts back. `history.state`
|
|
149
|
+
* holds only an entry's id. It once kept the same facts in several places (the address bar written
|
|
150
|
+
* before the fetch, `current_url`, `doc_key`, a scroll saved on push only), and they drifted.
|
|
151
|
+
*
|
|
152
|
+
* - An entry is written when a page goes in (the navigation's commit), never before its fetch: a
|
|
153
|
+
* download or a 204 adds none (#297), and the address bar is the page on screen.
|
|
154
|
+
* - Leaving an entry keeps where its page was, on every move: a click, a back or forward step (#270).
|
|
155
|
+
* - An id this document never made (its state names another document's entry) is not this
|
|
156
|
+
* document's to show: the browser loads it whole. An entry with no id (the browser's own fragment
|
|
157
|
+
* jump, an app's own `pushState`) is taken over when the router meets it.
|
|
158
|
+
*/
|
|
159
|
+
/** The state of the entry the browser is on, named by `id`: the id goes beside what the app (or
|
|
160
|
+
* Kit) keeps there, never over it (#375). */
|
|
161
|
+
function named(id) {
|
|
162
|
+
const kept = history.state;
|
|
163
|
+
return kept !== null && typeof kept === "object" ? {
|
|
164
|
+
...kept,
|
|
165
|
+
ogygia: id
|
|
166
|
+
} : { ogygia: id };
|
|
167
|
+
}
|
|
168
|
+
var RouterHistory = class {
|
|
169
|
+
/** Names this document's entries apart from another document's (a reload's, say). */
|
|
170
|
+
#doc = Math.random().toString(36).slice(2, 10);
|
|
171
|
+
#n = 0;
|
|
172
|
+
#entries = /* @__PURE__ */ new Map();
|
|
173
|
+
#current = null;
|
|
174
|
+
/** The entry of the page on screen. */
|
|
175
|
+
get current() {
|
|
176
|
+
return this.#current ??= this.#add(new URL(location.href));
|
|
177
|
+
}
|
|
178
|
+
/** Takes over the entry the browser is on as the router starts: its state names it now. */
|
|
179
|
+
start() {
|
|
180
|
+
replace_state(named(this.current.id));
|
|
181
|
+
}
|
|
182
|
+
/** The entry a history state names: this model's, `'foreign'` (another document's), or null
|
|
183
|
+
* (no id: an entry the browser or the app made). */
|
|
184
|
+
find(state) {
|
|
185
|
+
const id = state?.ogygia;
|
|
186
|
+
if (typeof id !== "string") return null;
|
|
187
|
+
return this.#entries.get(id) ?? (id.startsWith(this.#doc + ".") ? null : "foreign");
|
|
188
|
+
}
|
|
189
|
+
/** The page on screen is being left: keep where it is. */
|
|
190
|
+
leave() {
|
|
191
|
+
const entry = this.current;
|
|
192
|
+
entry.scroll = {
|
|
193
|
+
x: scrollX,
|
|
194
|
+
y: scrollY
|
|
195
|
+
};
|
|
196
|
+
const active = typeof document === "undefined" ? null : document.activeElement;
|
|
197
|
+
entry.focus = active && active !== document.body && active.id ? active.id : null;
|
|
198
|
+
}
|
|
199
|
+
/** A page goes in at `url` (the browser's entry is written now). `push`: a new entry after the
|
|
200
|
+
* one left; `replace`: the current one at a new address; `pop`: `to`, the entry a back or
|
|
201
|
+
* forward step is on. */
|
|
202
|
+
commit(url, move, to) {
|
|
203
|
+
if (move === "push") {
|
|
204
|
+
this.leave();
|
|
205
|
+
const entry = this.#add(url);
|
|
206
|
+
push_state({ ogygia: entry.id }, url.href);
|
|
207
|
+
return this.#current = entry;
|
|
208
|
+
}
|
|
209
|
+
const entry = move === "pop" && to ? to : this.current;
|
|
210
|
+
entry.url = url;
|
|
211
|
+
this.#current = entry;
|
|
212
|
+
if (location.href !== url.href || history.state?.ogygia !== entry.id) replace_state(named(entry.id), url.href);
|
|
213
|
+
return entry;
|
|
214
|
+
}
|
|
215
|
+
/** The entry the browser stepped to, which no id names: this model's from now on. */
|
|
216
|
+
adopt(url) {
|
|
217
|
+
const entry = this.#add(url);
|
|
218
|
+
replace_state(named(entry.id));
|
|
219
|
+
return entry;
|
|
220
|
+
}
|
|
221
|
+
#add(url) {
|
|
222
|
+
const entry = {
|
|
223
|
+
id: this.#doc + "." + ++this.#n,
|
|
224
|
+
url,
|
|
225
|
+
scroll: null,
|
|
226
|
+
focus: null
|
|
227
|
+
};
|
|
228
|
+
this.#entries.set(entry.id, entry);
|
|
229
|
+
return entry;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
/** Run `fn` once, when the browser is idle or after `cap` ms, whichever is first, unless `page` (a
|
|
233
|
+
* page visit's scope) ends first. */
|
|
234
|
+
function on_idle(page, fn, cap = 1e3) {
|
|
235
|
+
const run = () => {
|
|
236
|
+
idle();
|
|
237
|
+
capped();
|
|
238
|
+
fn();
|
|
239
|
+
};
|
|
240
|
+
const idle = page.timeout(run, "idle");
|
|
241
|
+
const capped = page.timeout(run, cap);
|
|
140
242
|
}
|
|
141
|
-
let nav_promise = null;
|
|
142
243
|
let loaded_nav = null;
|
|
244
|
+
const load_nav = flights({
|
|
245
|
+
name: "navigation",
|
|
246
|
+
run: import_again(() => import("./router-nav.js")),
|
|
247
|
+
retry: download
|
|
248
|
+
});
|
|
249
|
+
/** (Never STALE: nothing drops the navigation's load; a caller treats it as one that failed.) */
|
|
143
250
|
function nav() {
|
|
144
|
-
slots.router_link ??= {
|
|
145
|
-
|
|
146
|
-
jump_to_hash,
|
|
147
|
-
push_state,
|
|
148
|
-
replace_state
|
|
149
|
-
};
|
|
150
|
-
if (!nav_promise) nav_promise = import("./router-nav.js").then((m) => loaded_nav = m, (err) => {
|
|
151
|
-
nav_promise = null;
|
|
152
|
-
throw err;
|
|
153
|
-
});
|
|
154
|
-
return nav_promise;
|
|
251
|
+
slots.router_link ??= { jump_to_hash };
|
|
252
|
+
return load_nav.get().then((m) => m === STALE ? m : loaded_nav = m);
|
|
155
253
|
}
|
|
156
254
|
var SpaRouter = class {
|
|
157
255
|
#started = false;
|
|
158
256
|
#before_hooks = /* @__PURE__ */ new Set();
|
|
159
257
|
#after_hooks = /* @__PURE__ */ new Set();
|
|
160
|
-
/** href of the navigation in flight (set before the fetch, cleared when applied or aborted) */
|
|
161
|
-
nav_target = null;
|
|
162
|
-
/** The in-flight navigation's cancel handle: router-nav bumps `nav_gen` and sets `nav_abort` for
|
|
163
|
-
* each navigation it starts, and stops at its next step once `nav_gen` moved on. */
|
|
164
|
-
nav_gen = 0;
|
|
165
|
-
nav_abort = null;
|
|
166
|
-
#viewport_io = null;
|
|
167
|
-
#viewport_seen = /* @__PURE__ */ new WeakSet();
|
|
168
|
-
/** pathname+search of the document currently in the DOM (hash ignored). */
|
|
169
|
-
doc_key = "";
|
|
170
258
|
/**
|
|
171
|
-
*
|
|
259
|
+
* THE NAVIGATION, one machine (./navigation.ts): idle, claimed (the click, while the navigation
|
|
260
|
+
* chunk loads), loading (its fetch through its swap), settling (swapped: the last steps run). A
|
|
261
|
+
* new click is a new visit, so whatever the old one still had in flight sees it is not current
|
|
262
|
+
* (`nav.now !== mine`) and stops, and its fetch is aborted. It was three fields kept by hand; two
|
|
263
|
+
* clicks during the first chunk load once held the same generation and the last one was lost.
|
|
264
|
+
*/
|
|
265
|
+
nav = create_navigation();
|
|
266
|
+
/** href of the router's navigation in flight (claimed or loading; cleared once it applied,
|
|
267
|
+
* ended, or went to the browser: `hand_off`) */
|
|
268
|
+
get nav_target() {
|
|
269
|
+
const s = this.nav.now;
|
|
270
|
+
return s.kind === "claimed" || s.kind === "loading" ? s.href : null;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* The anchors whose click the router took (prevented, and navigated), and the address it took each
|
|
274
|
+
* to. A link component clicks its inner `<a>` again after the visitor's click, and that second
|
|
275
|
+
* click may never reach the router's `document` listener: a click event that is not composed
|
|
276
|
+
* stays in the component's shadow tree, and an anchor the swap took out of the page is in no
|
|
277
|
+
* document. The browser then followed the link itself, a full load after a click the router had
|
|
278
|
+
* already prevented (#263). So an anchor the router took a click from listens itself, for the
|
|
279
|
+
* clicks `document` will not hear (`#on_anchor_click`).
|
|
280
|
+
*/
|
|
281
|
+
#taken = /* @__PURE__ */ new WeakMap();
|
|
282
|
+
/** The visitor's history, as the router keeps it (`RouterHistory`): the rest is read from it. */
|
|
283
|
+
history = new RouterHistory();
|
|
284
|
+
/**
|
|
285
|
+
* Full URL of the page on screen: its history entry's. `navigate()` uses this as its `from`
|
|
172
286
|
* instead of `location.href`, because on a `popstate` (back/forward) the browser has ALREADY
|
|
173
287
|
* changed `location` to the target — so `location.href` would equal the target and the
|
|
174
288
|
* same-document guard would wrongly bail into the hash-only branch, never swapping the body
|
|
175
289
|
* (browser back left the old page's DOM in place). POP-FROM.
|
|
176
290
|
*/
|
|
177
|
-
current_url
|
|
291
|
+
get current_url() {
|
|
292
|
+
return this.history.current.url;
|
|
293
|
+
}
|
|
294
|
+
/** pathname+search of the page on screen (hash ignored). */
|
|
295
|
+
get doc_key() {
|
|
296
|
+
return document_key(this.current_url);
|
|
297
|
+
}
|
|
178
298
|
beforeNavigate(fn) {
|
|
179
299
|
this.#before_hooks.add(fn);
|
|
180
300
|
return () => this.#before_hooks.delete(fn);
|
|
@@ -188,7 +308,9 @@ var SpaRouter = class {
|
|
|
188
308
|
type: "enter",
|
|
189
309
|
willUnload: false
|
|
190
310
|
});
|
|
191
|
-
} catch {
|
|
311
|
+
} catch (error) {
|
|
312
|
+
report_thrown(error);
|
|
313
|
+
}
|
|
192
314
|
return () => this.#after_hooks.delete(fn);
|
|
193
315
|
}
|
|
194
316
|
/**
|
|
@@ -199,62 +321,80 @@ var SpaRouter = class {
|
|
|
199
321
|
* swapping left the URL on the old page and `/plain` in the DOM).
|
|
200
322
|
*/
|
|
201
323
|
supersede() {
|
|
202
|
-
if (this.
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
324
|
+
if (this.nav.now.kind !== "idle") this.nav.go("idle");
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* GIVE `visit`'s NAVIGATION TO THE BROWSER — a page the router will not render (a csr=true Kit
|
|
328
|
+
* page, a page that is not ogygia's, another build), or one it could not fetch or load its code
|
|
329
|
+
* for: a document navigation to `url`, from the document on screen (`from`).
|
|
330
|
+
*
|
|
331
|
+
* It ENDS the visit, on every path: from here on the navigation is the browser's, never the
|
|
332
|
+
* router's in flight. The browser may well keep this page on screen (a file download, a 204, a
|
|
333
|
+
* body it does not show), and then nothing else ends it: left `loading`, every later click on the
|
|
334
|
+
* link joined it and was swallowed (#266). A visit already left (a newer click took over, or back
|
|
335
|
+
* to the page on screen) hands nothing off: the browser never goes where the visitor no longer is.
|
|
336
|
+
*
|
|
337
|
+
* The router writes an entry only when a page goes in, so a click handed off has none: the
|
|
338
|
+
* browser's navigation is a new entry of its own, and one it keeps the page on screen for adds
|
|
339
|
+
* none (#297). A back or forward step is already on its entry, and a refresh on the one on
|
|
340
|
+
* screen: the browser's navigation REPLACES it, so its document holds the entry alone, never
|
|
341
|
+
* shares it with the router's page (back from a csr=true page then loads the router page, #259).
|
|
342
|
+
* So does a commit that failed after it wrote the entry (the address is already `url`).
|
|
343
|
+
*
|
|
344
|
+
* `error`: the navigation failed part way (its devalue did not load, its swap threw, #276). It
|
|
345
|
+
* is said (the page's error handler, as an uncaught throw would be), and the browser loads the
|
|
346
|
+
* page whole.
|
|
347
|
+
*/
|
|
348
|
+
hand_off(url, visit, move, error) {
|
|
349
|
+
if (this.nav.now !== visit) return;
|
|
350
|
+
this.nav.go("idle");
|
|
351
|
+
if (error !== void 0) report_thrown(error);
|
|
352
|
+
if (move === "push" && location.href !== url.href) location.assign(url.href);
|
|
353
|
+
else location.replace(url.href);
|
|
207
354
|
}
|
|
208
355
|
bust_page_cache() {
|
|
209
356
|
loaded_nav?.bust_page_cache();
|
|
210
357
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
358
|
+
/** The page at `href`, or `null` when there is none to have: the fetch failed (./router-nav.ts
|
|
359
|
+
* `fetch_page`), or the navigation code itself did not load. The prefetches (a hover, a
|
|
360
|
+
* viewport link, `preloadData`) start it and never wait on it: a rejection here was uncaught. */
|
|
214
361
|
fetch_page(href, signal, purpose) {
|
|
215
|
-
return nav().then((n) => n.fetch_page(href, signal, purpose));
|
|
362
|
+
return nav().then((n) => n === STALE ? null : n.fetch_page(href, signal, purpose), () => null);
|
|
216
363
|
}
|
|
217
364
|
async navigate(url, opts = {}) {
|
|
218
365
|
const { push = true, type = "link", replace = false } = opts;
|
|
219
|
-
const from = this.current_url
|
|
366
|
+
const from = this.current_url;
|
|
367
|
+
const move = opts.pop ? "pop" : push && !replace ? "push" : "replace";
|
|
220
368
|
if (same_document(url, from) && !replace) {
|
|
221
369
|
if (!this.run_before(from, url, type)) return;
|
|
222
370
|
this.supersede();
|
|
223
|
-
if (
|
|
224
|
-
...history.state,
|
|
225
|
-
ogygia: true
|
|
226
|
-
}, url.href);
|
|
227
|
-
else if (url.href !== location.href) replace_state({
|
|
228
|
-
...history.state,
|
|
229
|
-
ogygia: true
|
|
230
|
-
}, url.href);
|
|
371
|
+
if (url.href !== location.href) this.history.commit(url, move);
|
|
231
372
|
jump_to_hash(url.hash);
|
|
232
|
-
this.current_url = url;
|
|
233
373
|
this.run_after(from, url, type);
|
|
234
374
|
return;
|
|
235
375
|
}
|
|
236
|
-
this.
|
|
237
|
-
|
|
376
|
+
if (!this.run_before(from, url, type)) return;
|
|
377
|
+
this.nav.go("claimed", { href: url.href });
|
|
378
|
+
const mine = this.nav.now;
|
|
238
379
|
let n;
|
|
239
380
|
try {
|
|
240
381
|
n = await nav();
|
|
241
382
|
} catch {
|
|
242
|
-
|
|
243
|
-
location.href = url.href;
|
|
244
|
-
return;
|
|
383
|
+
return this.hand_off(url, mine, move);
|
|
245
384
|
}
|
|
246
|
-
if (
|
|
247
|
-
|
|
385
|
+
if (n === STALE) return this.hand_off(url, mine, move);
|
|
386
|
+
if (this.nav.now !== mine) return;
|
|
387
|
+
return n.navigate(this, url, from, opts, mine);
|
|
248
388
|
}
|
|
249
389
|
goto(url, opts = {}) {
|
|
250
390
|
const target = new URL(url, location.href);
|
|
251
|
-
if (target.protocol !== "http:" && target.protocol !== "https:") throw new
|
|
391
|
+
if (target.protocol !== "http:" && target.protocol !== "https:") throw new OgygiaError("goto_not_http");
|
|
252
392
|
if (target.origin !== location.origin) {
|
|
253
393
|
if (opts.external) {
|
|
254
394
|
location.assign(target.href);
|
|
255
395
|
return Promise.resolve();
|
|
256
396
|
}
|
|
257
|
-
throw new
|
|
397
|
+
throw new OgygiaError("goto_cross_origin");
|
|
258
398
|
}
|
|
259
399
|
return this.navigate(target, {
|
|
260
400
|
push: !opts.replaceState,
|
|
@@ -263,7 +403,7 @@ var SpaRouter = class {
|
|
|
263
403
|
});
|
|
264
404
|
}
|
|
265
405
|
invalidateAll() {
|
|
266
|
-
return nav().then((n) => n.invalidate_all());
|
|
406
|
+
return nav().then((n) => n === STALE ? void 0 : n.invalidate_all(this.current_url));
|
|
267
407
|
}
|
|
268
408
|
invalidate() {
|
|
269
409
|
return this.invalidateAll();
|
|
@@ -286,75 +426,110 @@ var SpaRouter = class {
|
|
|
286
426
|
return Promise.resolve();
|
|
287
427
|
}
|
|
288
428
|
disableScrollHandling() {
|
|
289
|
-
if (typeof console !== "undefined")
|
|
429
|
+
if (typeof console !== "undefined") warn("scroll_handling_noop");
|
|
290
430
|
}
|
|
291
431
|
pushState() {
|
|
292
|
-
|
|
432
|
+
warn("shallow_routing_unsupported", { method: "pushState" });
|
|
293
433
|
}
|
|
294
434
|
replaceState() {
|
|
295
|
-
|
|
435
|
+
warn("shallow_routing_unsupported", { method: "replaceState" });
|
|
296
436
|
}
|
|
297
437
|
start() {
|
|
298
438
|
if (this.#started || typeof document === "undefined") return;
|
|
299
439
|
if (!document.querySelector("meta[name=\"ogygia-router\"]")) return;
|
|
300
440
|
if (kit_hydrates_page()) return;
|
|
301
441
|
this.#started = true;
|
|
302
|
-
this.
|
|
303
|
-
|
|
442
|
+
this.history.start();
|
|
443
|
+
if (RULES) watch_router(this);
|
|
304
444
|
const body = document.body;
|
|
305
445
|
if (body) record_server_body(body, body, Array.from(body.childNodes));
|
|
306
|
-
document.addEventListener("click", (event) =>
|
|
307
|
-
const anchor = anchor_of(event);
|
|
308
|
-
const url = this.#should_intercept(event, anchor);
|
|
309
|
-
if (!url) return;
|
|
310
|
-
if (this.nav_target === url.href) {
|
|
311
|
-
event.preventDefault();
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
if (same_document(url, new URL(location.href))) {
|
|
315
|
-
const kind = same_document_link(url, new URL(location.href), this.nav_target);
|
|
316
|
-
if (kind === "hash") {
|
|
317
|
-
if (!hash_target_exists(url.hash)) {
|
|
318
|
-
event.preventDefault();
|
|
319
|
-
if (location.hash !== url.hash) {
|
|
320
|
-
push_state(history.state || {}, url.href);
|
|
321
|
-
window.dispatchEvent(new HashChangeEvent("hashchange", {
|
|
322
|
-
oldURL: location.href,
|
|
323
|
-
newURL: url.href
|
|
324
|
-
}));
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
event.preventDefault();
|
|
330
|
-
if (kind === "refresh") this.navigate(url, {
|
|
331
|
-
push: false,
|
|
332
|
-
replace: true
|
|
333
|
-
});
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
event.preventDefault();
|
|
337
|
-
this.navigate(url, { push: true });
|
|
338
|
-
});
|
|
446
|
+
document.addEventListener("click", (event) => this.#on_click(event, anchor_of(event)));
|
|
339
447
|
this.#install_prefetch();
|
|
340
448
|
window.addEventListener("popstate", () => {
|
|
341
449
|
const url = new URL(location.href);
|
|
450
|
+
const found = this.history.find(history.state);
|
|
451
|
+
if (found === "foreign") {
|
|
452
|
+
this.supersede();
|
|
453
|
+
location.reload();
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
this.history.leave();
|
|
457
|
+
const entry = found ?? this.history.adopt(url);
|
|
342
458
|
if (document_key(url) === this.doc_key) {
|
|
343
459
|
this.supersede();
|
|
344
|
-
|
|
460
|
+
this.history.commit(url, "pop", entry);
|
|
345
461
|
return;
|
|
346
462
|
}
|
|
347
|
-
const pop_scroll = history.state?.scroll || null;
|
|
348
463
|
this.navigate(url, {
|
|
349
464
|
push: false,
|
|
350
|
-
|
|
465
|
+
pop: entry,
|
|
351
466
|
type: "popstate"
|
|
352
467
|
});
|
|
353
468
|
});
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
469
|
+
}
|
|
470
|
+
/** A click on `anchor`: the router's, or the browser's. A click it prevents always ends in a
|
|
471
|
+
* navigation, the one it starts or the one it joins. */
|
|
472
|
+
#on_click(event, anchor) {
|
|
473
|
+
const url = this.#should_intercept(event, anchor);
|
|
474
|
+
if (!url || !anchor) return;
|
|
475
|
+
if (this.#joins(event, anchor, url)) {
|
|
476
|
+
event.preventDefault();
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
if (same_document(url, new URL(location.href))) {
|
|
480
|
+
const kind = same_document_link(url, new URL(location.href), this.nav_target);
|
|
481
|
+
if (kind === "hash") {
|
|
482
|
+
if (!hash_target_exists(url.hash)) {
|
|
483
|
+
event.preventDefault();
|
|
484
|
+
if (location.hash !== url.hash) {
|
|
485
|
+
const old = location.href;
|
|
486
|
+
this.history.commit(url, "push");
|
|
487
|
+
window.dispatchEvent(new HashChangeEvent("hashchange", {
|
|
488
|
+
oldURL: old,
|
|
489
|
+
newURL: url.href
|
|
490
|
+
}));
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
event.preventDefault();
|
|
496
|
+
this.#take(anchor, url);
|
|
497
|
+
if (kind === "refresh") this.navigate(url, {
|
|
498
|
+
push: false,
|
|
499
|
+
replace: true
|
|
500
|
+
});
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
event.preventDefault();
|
|
504
|
+
this.#take(anchor, url);
|
|
505
|
+
this.navigate(url, { push: true });
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Does this click join a navigation the router already has? Its own in flight to the click's
|
|
509
|
+
* address (a double click, or a link component re-dispatching the visitor's click on its inner
|
|
510
|
+
* `<a>`): never one it gave the browser (`hand_off` ends that visit), so a link to a download is
|
|
511
|
+
* a new navigation each time; or
|
|
512
|
+
* the one this very anchor started, to the page now on screen, when the click is a re-dispatch
|
|
513
|
+
* (untrusted: the component's, never the visitor's — a visitor's click on a link to the page they
|
|
514
|
+
* are on refreshes it).
|
|
515
|
+
*/
|
|
516
|
+
#joins(event, anchor, url) {
|
|
517
|
+
if (this.nav_target === url.href) return true;
|
|
518
|
+
return !event.isTrusted && this.#taken.get(anchor) === url.href && same_document(url, this.current_url);
|
|
519
|
+
}
|
|
520
|
+
/** The router took a click on `anchor` to `url`: from now on the anchor hears its own clicks too. */
|
|
521
|
+
#take(anchor, url) {
|
|
522
|
+
if (!this.#taken.has(anchor)) anchor.addEventListener("click", (event) => this.#on_anchor_click(event, anchor));
|
|
523
|
+
this.#taken.set(anchor, url.href);
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* A click on an anchor the router took a click from, heard on the anchor itself. One that reaches
|
|
527
|
+
* `document` is the document listener's (a component on the way may still prevent it); one that
|
|
528
|
+
* cannot — not composed and inside a shadow tree, or on an anchor that is in no document — is
|
|
529
|
+
* handled here, the same way.
|
|
530
|
+
*/
|
|
531
|
+
#on_anchor_click(event, anchor) {
|
|
532
|
+
if (!(anchor.isConnected && (event.composed || anchor.getRootNode() === document))) this.#on_click(event, anchor);
|
|
358
533
|
}
|
|
359
534
|
#should_intercept(event, anchor) {
|
|
360
535
|
if (event.defaultPrevented) return false;
|
|
@@ -381,7 +556,9 @@ var SpaRouter = class {
|
|
|
381
556
|
};
|
|
382
557
|
for (const fn of this.#before_hooks) try {
|
|
383
558
|
fn(nav);
|
|
384
|
-
} catch {
|
|
559
|
+
} catch (error) {
|
|
560
|
+
report_thrown(error);
|
|
561
|
+
}
|
|
385
562
|
return !cancelled;
|
|
386
563
|
}
|
|
387
564
|
run_after(from, to, type) {
|
|
@@ -392,7 +569,9 @@ var SpaRouter = class {
|
|
|
392
569
|
type,
|
|
393
570
|
willUnload: false
|
|
394
571
|
});
|
|
395
|
-
} catch {
|
|
572
|
+
} catch (error) {
|
|
573
|
+
report_thrown(error);
|
|
574
|
+
}
|
|
396
575
|
}
|
|
397
576
|
#install_prefetch() {
|
|
398
577
|
document.addEventListener("mouseover", (event) => {
|
|
@@ -407,34 +586,27 @@ var SpaRouter = class {
|
|
|
407
586
|
document.addEventListener("touchstart", on_press, { passive: true });
|
|
408
587
|
this.scan_preload_links();
|
|
409
588
|
}
|
|
410
|
-
/** After start + every navigation: drop detached IO targets, then re-observe the live body. */
|
|
411
|
-
#reset_viewport_io() {
|
|
412
|
-
this.#viewport_io?.disconnect();
|
|
413
|
-
this.#viewport_seen = /* @__PURE__ */ new WeakSet();
|
|
414
|
-
this.#viewport_io = new IntersectionObserver((entries) => {
|
|
415
|
-
for (const e of entries) if (e.isIntersecting) {
|
|
416
|
-
this.#viewport_io.unobserve(e.target);
|
|
417
|
-
this.#warm_anchor(e.target);
|
|
418
|
-
}
|
|
419
|
-
}, { rootMargin: "0px" });
|
|
420
|
-
}
|
|
421
589
|
/**
|
|
422
590
|
* After start + every navigation, in IDLE: eager links warm now; viewport links get observed.
|
|
423
591
|
* Only anchors under a preload-marked element are read — an unmarked anchor has no trigger. Idle,
|
|
424
592
|
* because the scan competes with the freshly swapped page's own islands for the main thread and
|
|
425
|
-
* a warm is speculation by definition.
|
|
593
|
+
* a warm is speculation by definition. The scan and its observer are the page visit's
|
|
594
|
+
* (./session.ts `PageVisit`): the next page's swap disconnects it, so the anchors of a page left
|
|
595
|
+
* behind are never held, and a scan still waiting for idle time when its page is left never runs.
|
|
426
596
|
*/
|
|
427
597
|
scan_preload_links() {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
598
|
+
const page = runtime_session.visit.scope;
|
|
599
|
+
on_idle(page, () => {
|
|
600
|
+
const io = page.watch(new IntersectionObserver((entries) => {
|
|
601
|
+
for (const e of entries) if (e.isIntersecting) {
|
|
602
|
+
io.unobserve(e.target);
|
|
603
|
+
this.#warm_anchor(e.target);
|
|
604
|
+
}
|
|
605
|
+
}, { rootMargin: "0px" }));
|
|
431
606
|
for (const anchor of Array.from(document.querySelectorAll(PRELOAD_MARKED_ANCHORS))) {
|
|
432
607
|
const rank = this.#preload_rank(anchor);
|
|
433
608
|
if (rank === PRELOAD_RANK.eager) this.#warm_anchor(anchor);
|
|
434
|
-
else if (rank === PRELOAD_RANK.viewport
|
|
435
|
-
this.#viewport_seen.add(anchor);
|
|
436
|
-
this.#viewport_io.observe(anchor);
|
|
437
|
-
}
|
|
609
|
+
else if (rank === PRELOAD_RANK.viewport) io.observe(anchor);
|
|
438
610
|
}
|
|
439
611
|
});
|
|
440
612
|
}
|
|
@@ -493,10 +665,6 @@ function afterNavigate(fn) {
|
|
|
493
665
|
function bust_page_cache() {
|
|
494
666
|
spa.bust_page_cache();
|
|
495
667
|
}
|
|
496
|
-
/** Test helper — current warmed entry count. */
|
|
497
|
-
function _page_cache_size() {
|
|
498
|
-
return spa._page_cache_size();
|
|
499
|
-
}
|
|
500
668
|
/**
|
|
501
669
|
* Programmatic same-origin navigation. Mirrors Kit's `goto()` subset.
|
|
502
670
|
* @param url - Absolute or relative URL (http(s) only).
|
|
@@ -593,4 +761,4 @@ function install() {
|
|
|
593
761
|
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", start, { once: true });
|
|
594
762
|
else start();
|
|
595
763
|
}
|
|
596
|
-
export {
|
|
764
|
+
export { RouterHistory, SpaRouter, afterNavigate, anchor_of, beforeNavigate, bust_page_cache, disableScrollHandling, document_key, goto, install, invalidate, invalidateAll, jump_to_hash, preloadCode, preloadData, pushState, push_state, reload_opt_out, replaceState, replace_state, same_document, same_document_link, startRouter };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Rules } from "../rules.js";
|
|
2
|
+
type RuntimeRule = "rule_region_mounted_twice" | "rule_address_bar_drift" | "rule_old_page_open" | "rule_failure_uncoded" | "rule_parse_upgraded";
|
|
3
|
+
/** The runtime's rules. */
|
|
4
|
+
export declare const runtime_rules: Rules<RuntimeRule>;
|
|
5
|
+
/** A region element mounted its island. */
|
|
6
|
+
export declare function note_mount(region: Element): void;
|
|
7
|
+
/** A region element let its island go. */
|
|
8
|
+
export declare function note_unmount(region: Element): void;
|
|
9
|
+
type Visit = {
|
|
10
|
+
readonly scope: {
|
|
11
|
+
readonly closed: boolean;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
/** A page visit was made (`current`: it is the page on screen's now). */
|
|
15
|
+
export declare function note_visit(visit: Visit, is_current: boolean): void;
|
|
16
|
+
/** What the rule reads of the router: its navigation, and its history model's page on screen. */
|
|
17
|
+
interface RouterView {
|
|
18
|
+
readonly nav: {
|
|
19
|
+
readonly now: {
|
|
20
|
+
readonly kind: string;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
readonly history: {
|
|
24
|
+
readonly current: {
|
|
25
|
+
readonly id: string;
|
|
26
|
+
readonly url: URL;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** The router started: its history model is checked against the address bar. */
|
|
31
|
+
export declare function watch_router(router: RouterView): void;
|
|
32
|
+
/** Rules on: `globalThis.__ogygia_rules()` gives what is broken now (for e2e); in dev they are also
|
|
33
|
+
* checked after machine moves, and what broke is said with its code. */
|
|
34
|
+
export declare function install_rules(dev: boolean): void;
|
|
35
|
+
export {};
|