pageturn 0.1.1 → 0.2.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.
Files changed (75) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +174 -4
  3. package/dist/astro.js +0 -2
  4. package/dist/chunks/chunk-3ABPYO7H.js +43 -0
  5. package/dist/chunks/chunk-6FXXBNAF.js +197 -0
  6. package/dist/chunks/chunk-AEOAXMU3.js +33 -0
  7. package/dist/chunks/chunk-BOM5VXA5.js +167 -0
  8. package/dist/chunks/chunk-C3T7BGNG.js +30 -0
  9. package/dist/chunks/chunk-FYSSNYA4.js +17 -0
  10. package/dist/chunks/chunk-JHM7F4CH.js +172 -0
  11. package/dist/chunks/chunk-LM3XLSCW.js +799 -0
  12. package/dist/chunks/{chunk-QJ7GGFM4.js → chunk-YAL4MNCU.js} +4 -6
  13. package/dist/core.js +4 -3
  14. package/dist/dev/chunks/chunk-3JAWIMHB.js +184 -0
  15. package/dist/dev/chunks/chunk-3SU4CIKL.js +799 -0
  16. package/dist/dev/chunks/chunk-AEOAXMU3.js +33 -0
  17. package/dist/dev/chunks/chunk-C3T7BGNG.js +30 -0
  18. package/dist/dev/chunks/chunk-FYSSNYA4.js +17 -0
  19. package/dist/dev/chunks/chunk-I4SBUABE.js +175 -0
  20. package/dist/dev/chunks/chunk-U3XN54YY.js +219 -0
  21. package/dist/dev/chunks/chunk-Y6TD2T53.js +43 -0
  22. package/dist/dev/chunks/chunk-YAL4MNCU.js +22 -0
  23. package/dist/dev/core.js +19 -0
  24. package/dist/dev/index.js +21 -0
  25. package/dist/dev/morph-markup.js +34 -0
  26. package/dist/dev/morph.js +457 -0
  27. package/dist/dev/native.js +3 -0
  28. package/dist/dev/next.js +79 -0
  29. package/dist/dev/nuxt-plugin.js +24 -0
  30. package/dist/dev/prefetch.js +2 -0
  31. package/dist/dev/react-router.js +46 -0
  32. package/dist/dev/router.js +9 -0
  33. package/dist/dev/sveltekit.js +32 -0
  34. package/dist/dev/testing.js +48 -0
  35. package/dist/dev/turbo.js +49 -0
  36. package/dist/dev/vue.js +11 -0
  37. package/dist/head-core.js +1 -1
  38. package/dist/head-string.js +6 -6
  39. package/dist/head.js +1 -1
  40. package/dist/index.js +6 -5
  41. package/dist/morph-head.js +1 -1
  42. package/dist/morph-markup.js +11 -9
  43. package/dist/morph.js +145 -287
  44. package/dist/native.js +3 -2
  45. package/dist/next.js +79 -0
  46. package/dist/nuxt-plugin.js +24 -0
  47. package/dist/nuxt.js +34 -0
  48. package/dist/pageturn-morph.js +1 -1
  49. package/dist/pageturn.js +1 -1
  50. package/dist/prefetch.js +2 -2
  51. package/dist/react-router.js +46 -0
  52. package/dist/router.js +5 -183
  53. package/dist/sveltekit-server.js +22 -0
  54. package/dist/sveltekit.js +32 -0
  55. package/dist/testing.js +12 -22
  56. package/dist/turbo.js +49 -0
  57. package/dist/types/astro/integration.d.ts +0 -4
  58. package/dist/types/common/dev.d.ts +6 -0
  59. package/dist/types/common/page.d.ts +5 -0
  60. package/dist/types/common/registry.d.ts +3 -1
  61. package/dist/types/next/next.d.ts +27 -0
  62. package/dist/types/nuxt/module.d.ts +32 -0
  63. package/dist/types/react-router/react-router.d.ts +12 -0
  64. package/dist/types/router/router.d.ts +4 -2
  65. package/dist/types/sveltekit/server.d.ts +21 -0
  66. package/dist/types/sveltekit/sveltekit.d.ts +32 -0
  67. package/dist/types/turbo/turbo.d.ts +1 -0
  68. package/dist/types/vue/vue.d.ts +36 -0
  69. package/dist/vue.js +11 -0
  70. package/package.json +71 -4
  71. package/dist/chunks/chunk-237GNQNZ.js +0 -37
  72. package/dist/chunks/chunk-7LMNJ25V.js +0 -244
  73. package/dist/chunks/chunk-QC36TQP3.js +0 -24
  74. package/dist/chunks/chunk-WTHN3UIQ.js +0 -1000
  75. package/dist/chunks/chunk-YBAP3PHO.js +0 -247
package/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # pageturn
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - f03333d: Warnings in development: in the build package.json's `development` condition serves,
8
+ pageturn says in the console why a move did not play (no head script, reduced motion, a
9
+ client router with no adapter, a return by Back with no animation, a router's own view
10
+ transition alongside pageturn's). Production builds carry none of it.
11
+ - f03333d: Next.js: `pageturn/next` plays pageturn's moves on the App Router. Its `Link` (as
12
+ `next/link`) and `usePageturnRouter()` (as `useRouter()`) play the leave before the router
13
+ navigates, and `<Pageturn />` in the root layout says when each page is in the DOM. On
14
+ any client router, the page reached is now recorded where the router shows it, base path
15
+ and trailing slash included, so a return by Back finds its `returnAs` there too.
16
+ - f03333d: Nuxt: the `pageturn/nuxt` module puts the head script in every page, adds the stylesheet,
17
+ and plays pageturn's moves on Nuxt's navigations (`<NuxtLink>`, `navigateTo()`, Back and
18
+ Forward), the page reached arriving once Nuxt's `<Suspense>` has resolved.
19
+ - 3b16406: Only `decline()` declines: a move's `leave` or a morph's `ends` that answers `false`, the
20
+ form from before `decline()`, now declines nothing (the types already refused it). Return
21
+ `turn.decline()` or `decline()` instead.
22
+ - f03333d: React Router: `pageturnMiddleware` on the root route and `usePageturn()` in the root
23
+ layout play pageturn's moves on React Router's navigations (`<Link>`, `navigate()`, Back
24
+ and Forward), the route's loaders running during the leave. `react` and `react-router`
25
+ are optional peer dependencies, used by this adapter only.
26
+ - f03333d: SvelteKit: `pageturn/sveltekit` plays pageturn's moves on SvelteKit's own navigations
27
+ (links, `goto()`, Back and Forward), and `pageturn/sveltekit/server` puts the head script
28
+ in every page. Every module of pageturn can now be imported on a server, where it does
29
+ nothing: a framework's layouts run there too.
30
+ - f03333d: Turbo: `pageturn/turbo` plays pageturn's moves on Turbo Drive's visits (links, forms,
31
+ `Turbo.visit()`, Back and Forward), holding Turbo's render until the leave has ended. On
32
+ a page Turbo has cached, the preview is not shown under the leave.
33
+ - f03333d: Vue Router: `pageturn/vue` plays pageturn's moves on the router's navigations
34
+ (`<RouterLink>`, `router.push()`, Back and Forward). On any client router, a navigation
35
+ to a place in the same page plays no move, and one started from code plays the default
36
+ move rather than asking each move's `match`.
37
+
3
38
  ## 0.1.1
4
39
 
5
40
  ### Patch Changes
package/README.md CHANGED
@@ -89,6 +89,153 @@ first in every page's `<head>`, so your own inline scripts can read
89
89
  The integration ships only what these options use. Your own scripts import from
90
90
  `pageturn/core` (see [Entries](#entries)).
91
91
 
92
+ ### SvelteKit
93
+
94
+ ```js
95
+ // src/hooks.server.js: the head script first in every page
96
+ import { pageturnHandle } from "pageturn/sveltekit/server";
97
+ export const handle = pageturnHandle();
98
+ ```
99
+
100
+ ```svelte
101
+ <!-- src/routes/+layout.svelte -->
102
+ <script>
103
+ import "pageturn/pageturn.css";
104
+ import { beforeNavigate, onNavigate } from "$app/navigation";
105
+ import { pageturn } from "pageturn/sveltekit";
106
+
107
+ pageturn({ beforeNavigate, onNavigate });
108
+ </script>
109
+ ```
110
+
111
+ SvelteKit's own navigations then play the same moves: links, `goto()`, Back and Forward.
112
+ The leave plays while SvelteKit loads the next page's data, and SvelteKit waits for it
113
+ before it updates the page. A link SvelteKit leaves to the browser
114
+ (`data-sveltekit-reload`) is a real navigation, and plays its move too. With
115
+ `pageturn/no-native.css` or your own CSS, `pageturnHandle({ native: false })`. Under a
116
+ Content-Security-Policy, add `headScriptHash` from `pageturn/head` to `kit.csp`. Turn off
117
+ any SvelteKit view-transition recipe: two systems animating one swap is the likeliest
118
+ bug.
119
+
120
+ ### Rails, with Turbo
121
+
122
+ ```html
123
+ <!-- In the layout's <head>: the head script, then the stylesheet -->
124
+ <script src="/pageturn/head.js"></script>
125
+ <link rel="stylesheet" href="/pageturn/pageturn.css">
126
+ ```
127
+
128
+ ```js
129
+ // app/javascript/application.js
130
+ import "@hotwired/turbo-rails";
131
+ import "pageturn/turbo";
132
+ ```
133
+
134
+ Turbo Drive's visits then play the same moves: links, forms, `Turbo.visit()`, Back and
135
+ Forward. The leave plays while Turbo fetches the page, and Turbo holds its render until
136
+ it has ended. On a page Turbo has cached, its preview is not shown under the leave: the
137
+ page arrives once, from its real render. A link Turbo leaves to the browser
138
+ (`data-turbo="false"`) is a real navigation, with its move. Copy `head.js` and the
139
+ stylesheet from `node_modules/pageturn/dist/` (or inline `headScript` from
140
+ `pageturn/head`), and remove Turbo's `view-transition` meta tag where pageturn plays.
141
+
142
+ ### Vue Router
143
+
144
+ ```js
145
+ // main.js, before the app mounts
146
+ import "pageturn/pageturn.css";
147
+ import { pageturn } from "pageturn/vue";
148
+ pageturn(router);
149
+ ```
150
+
151
+ ```html
152
+ <!-- index.html, first in <head> -->
153
+ <script src="/pageturn/head.js"></script>
154
+ ```
155
+
156
+ The router's navigations then play the same moves: `<RouterLink>`, `router.push()`,
157
+ Back and Forward. The leave starts with the navigation, and the router waits for it once
158
+ the route's components have loaded. A navigation a guard aborts puts the page back. A
159
+ `#place` in the same route plays no move. With Nuxt, use [`pageturn/nuxt`](#nuxt) rather
160
+ than calling this yourself.
161
+
162
+ ### Nuxt
163
+
164
+ ```ts
165
+ // nuxt.config.ts
166
+ export default defineNuxtConfig({
167
+ modules: ["pageturn/nuxt"], // or [["pageturn/nuxt", { css: "no-native" }]]
168
+ });
169
+ ```
170
+
171
+ The module puts the head script first in every page, adds the stylesheet (`css`:
172
+ `true`, `"no-native"` or `false`), and plays the same moves on Nuxt's navigations:
173
+ `<NuxtLink>`, `navigateTo()`, Back and Forward. The page reached arrives once Nuxt's
174
+ `<Suspense>` has resolved (`page:finish`). Turn off `experimental.viewTransition` and any
175
+ `pageTransition` where pageturn plays.
176
+
177
+ ### React Router
178
+
179
+ ```jsx
180
+ import { pageturnMiddleware, usePageturn } from "pageturn/react-router";
181
+
182
+ const router = createBrowserRouter([
183
+ { path: "/", Component: Root, middleware: [pageturnMiddleware], children },
184
+ ]);
185
+
186
+ function Root() {
187
+ usePageturn();
188
+ return <Outlet />;
189
+ }
190
+ ```
191
+
192
+ With the stylesheet imported once and the head script first in the document's `<head>`.
193
+ React Router 7.9 or later, with data routers (`createBrowserRouter`) or framework mode.
194
+ The middleware starts the leave as the navigation does, runs the route's loaders
195
+ meanwhile, and lets the router commit when both are done; the hook says when the route
196
+ reached is in the DOM. `<Link>`, `navigate()`, Back and Forward play the same moves. A
197
+ `<Link reloadDocument>` is a real navigation, with its move. Leave out React Router's
198
+ `viewTransition` where pageturn plays.
199
+
200
+ ### Next.js (App Router)
201
+
202
+ ```jsx
203
+ // app/layout.jsx
204
+ import { coreHeadScript } from "pageturn/head";
205
+ import { Pageturn } from "pageturn/next";
206
+ import "pageturn/no-native.css";
207
+
208
+ export default function Layout({ children }) {
209
+ return (
210
+ <html>
211
+ <head>
212
+ <script dangerouslySetInnerHTML={{ __html: coreHeadScript }} />
213
+ </head>
214
+ <body>
215
+ <Pageturn />
216
+ {children}
217
+ </body>
218
+ </html>
219
+ );
220
+ }
221
+ ```
222
+
223
+ ```jsx
224
+ import { Link, usePageturnRouter } from "pageturn/next";
225
+
226
+ <Link href="/work">Work</Link>; // as next/link
227
+ const router = usePageturnRouter(); // as useRouter()
228
+ router.push("/work");
229
+ ```
230
+
231
+ Next's router cannot wait for a leave, so pageturn's `Link` stops its navigation, plays
232
+ the leave while the page is prefetched, then asks the router again; `usePageturnRouter()`
233
+ does the same from code. `<Pageturn />` says when each page is in the DOM. Back and
234
+ Forward, a plain `next/link` and a bare `router.push` still work: the page arrives (under
235
+ its `returnAs` on Back), with no leave. Put the head script in a plain inline `<script>`:
236
+ `next/script`'s `beforeInteractive` runs it too late, from Next's own code. Next.js 16 or
237
+ later; leave out its experimental view transitions where pageturn plays.
238
+
92
239
  ### Plain HTML, any server
93
240
 
94
241
  ```html
@@ -682,6 +829,13 @@ scroll layer to stop):
682
829
  | `pageturn/morph` | One element from a page to the next. See [docs/morph.md](docs/morph.md) |
683
830
  | `pageturn/morph/markup` | The morph from `data-turn-morph`. Imported for its effect |
684
831
  | `pageturn/router` | The engine for client routers' navigations, which adapters drive. See [docs/router.md](docs/router.md) |
832
+ | `pageturn/sveltekit` | The SvelteKit adapter: `pageturn({ beforeNavigate, onNavigate })`, with the kernel |
833
+ | `pageturn/sveltekit/server` | `pageturnHandle()`: the head script in every page SvelteKit renders |
834
+ | `pageturn/turbo` | The Turbo Drive adapter, with the kernel. Imported for its effect |
835
+ | `pageturn/vue` | The Vue Router adapter: `pageturn(router)`, with the kernel |
836
+ | `pageturn/nuxt` | The Nuxt module: the head script, the stylesheet, and the Vue Router adapter on Nuxt's router |
837
+ | `pageturn/react-router` | The React Router adapter: `pageturnMiddleware` and `usePageturn()`, with the kernel |
838
+ | `pageturn/next` | The Next.js App Router adapter: `Link`, `usePageturnRouter()` and `<Pageturn />`, with the kernel |
685
839
  | `pageturn/testing` | For browser tests of a morph: the layer's parts by name, a travel held at a time or at its end |
686
840
  | `pageturn/astro` | The Astro integration |
687
841
 
@@ -691,6 +845,21 @@ And the files of `dist/` for a page with no bundler: `pageturn.css` or `no-nativ
691
845
  A site that turns prefetch off and writes its own moves imports `pageturn/core` only. The
692
846
  kernel needs the head script: without it, it does nothing, and links navigate normally.
693
847
 
848
+ ### Warnings in development
849
+
850
+ While you develop, pageturn says in the console why a move did not play, once each:
851
+
852
+ - the head script is missing from the page;
853
+ - `prefers-reduced-motion` is on, so nothing plays;
854
+ - a client router took a link and no adapter tells pageturn about it;
855
+ - a page returned to by Back under its `returnAs` has no animation of its own, so it
856
+ shows at once (Back cannot play a leave, which reads as a flash);
857
+ - a router plays its own view transition while pageturn plays a move.
858
+
859
+ They come from the build package.json's `development` condition serves, which Vite,
860
+ webpack and the frameworks on them pick while developing. A production build, and the
861
+ files of `dist/` loaded as they are, carry none of it.
862
+
694
863
  ### Content-Security-Policy
695
864
 
696
865
  - `script-src 'self'`: load `pageturn.js` as a file, or use `head: "external"` in Astro.
@@ -724,10 +893,11 @@ checked by hand before each release ([docs/release-checklist.md](docs/release-ch
724
893
 
725
894
  ## Questions
726
895
 
727
- **Does it work with Next.js, Nuxt, SvelteKit?** Not yet on the links their router
728
- handles: those do not load a page. `pageturn/router` is the engine for them, with the
729
- same moves and CSS; the adapters that connect each router are not written yet
730
- ([docs/router.md](docs/router.md)).
896
+ **Does it work with Next.js, Nuxt, SvelteKit?** Yes, with an adapter for each:
897
+ [SvelteKit](#sveltekit), [Turbo](#rails-with-turbo), [Vue Router](#vue-router),
898
+ [Nuxt](#nuxt), [React Router](#react-router) and [Next.js](#nextjs-app-router), all on
899
+ `pageturn/router` ([docs/router.md](docs/router.md)). pageturn can be imported where a
900
+ framework renders on the server: it does nothing there.
731
901
 
732
902
  **What if JavaScript fails, or the next page is slow?** The click is a real navigation.
733
903
  A leave that throws or takes too long navigates anyway. A page that does not come in
package/dist/astro.js CHANGED
@@ -109,7 +109,5 @@ function pageturn(options = {}) {
109
109
  };
110
110
  }
111
111
  export {
112
- HEAD_SCRIPT_PATH,
113
- MORPH_HEAD_SCRIPT_PATH,
114
112
  pageturn as default
115
113
  };
@@ -0,0 +1,43 @@
1
+ import {
2
+ startNavigation
3
+ } from "./chunk-BOM5VXA5.js";
4
+ import {
5
+ inBrowser
6
+ } from "./chunk-AEOAXMU3.js";
7
+
8
+ // src/vue/vue.ts
9
+ function pageturn(router, options = {}) {
10
+ if (!inBrowser) return () => {
11
+ };
12
+ let current = null, popped = !1, cancel = () => {
13
+ current?.cancelled(), current = null;
14
+ }, stops = [
15
+ // Back and Forward: the router's history says so before its guards run.
16
+ router.options.history.listen((_to, _from, info) => {
17
+ popped = info.type === "pop";
18
+ }),
19
+ router.beforeEach((to, from) => {
20
+ let via = popped ? "history" : "link";
21
+ popped = !1, from.matched.length !== 0 && (current = startNavigation({ to: router.resolve(to).href, via }));
22
+ }),
23
+ router.beforeResolve(() => current?.left),
24
+ router.afterEach((_to, _from, failure) => {
25
+ let turn = current;
26
+ if (current = null, !turn) return;
27
+ if (failure) {
28
+ turn.cancelled();
29
+ return;
30
+ }
31
+ let swap = () => turn.swapped();
32
+ options.whenShown ? options.whenShown(swap) : queueMicrotask(swap);
33
+ }),
34
+ router.onError(cancel)
35
+ ];
36
+ return () => {
37
+ for (let stop of stops) stop();
38
+ };
39
+ }
40
+
41
+ export {
42
+ pageturn
43
+ };
@@ -0,0 +1,197 @@
1
+ import {
2
+ STORE_VERSION,
3
+ choose,
4
+ followArrival,
5
+ leave,
6
+ stopFollowing,
7
+ tellArrival,
8
+ turnOf
9
+ } from "./chunk-JHM7F4CH.js";
10
+ import {
11
+ inBrowser,
12
+ reducesMotion
13
+ } from "./chunk-AEOAXMU3.js";
14
+ import {
15
+ registry
16
+ } from "./chunk-YAL4MNCU.js";
17
+
18
+ // src/kernel/kernel.ts
19
+ var store, current = null, stuckTimer;
20
+ function install() {
21
+ if (!inBrowser) return;
22
+ let published = window.pageturn?.j;
23
+ registry.g || published?.m !== STORE_VERSION || (store = published, registry.g = !0, addEventListener("click", putClickLast, !0), addEventListener("pointerdown", pressed, !0), addEventListener("pagehide", pageHidden, !0), addEventListener("pageshow", pageShown), followArrival());
24
+ }
25
+ function putClickLast() {
26
+ removeEventListener("click", clicked), addEventListener("click", clicked);
27
+ }
28
+ function linkIn(event) {
29
+ for (let target of event.composedPath()) {
30
+ if (target instanceof HTMLAnchorElement)
31
+ return target.hasAttribute("href") ? target : null;
32
+ if (target instanceof SVGAElement) return null;
33
+ }
34
+ return null;
35
+ }
36
+ function isEditable(event) {
37
+ let [target] = event.composedPath();
38
+ return target instanceof HTMLElement && target.isContentEditable;
39
+ }
40
+ function destinationOf(link) {
41
+ let target = link.target || document.querySelector("base[target]")?.getAttribute("target");
42
+ if (target && target !== "_self" || link.hasAttribute("download"))
43
+ return null;
44
+ let to = new URL(link.href), isThisPage = to.pathname === location.pathname && to.search === location.search;
45
+ return to.origin === location.origin && !isThisPage ? to : null;
46
+ }
47
+ function prefetch(to) {
48
+ registry.settings.prefetch && registry.u?.(to);
49
+ }
50
+ function pressed(event) {
51
+ if (event.button !== 0 || isEditable(event)) return;
52
+ let link = linkIn(event), to = link && destinationOf(link);
53
+ to && prefetch(to);
54
+ }
55
+ function clicked(event) {
56
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || isEditable(event))
57
+ return;
58
+ let link = linkIn(event), to = link && destinationOf(link);
59
+ if (!link || !to) return;
60
+ if (current) {
61
+ event.preventDefault();
62
+ return;
63
+ }
64
+ if (store.n(), event.detail === 0 && prefetch(to), reducesMotion()) return;
65
+ let { a: native } = registry, visit = {
66
+ link,
67
+ via: "link",
68
+ from: new URL(location.href),
69
+ to,
70
+ canHandOver: store.q()
71
+ }, name = choose(visit);
72
+ if (name === null) {
73
+ native?.c(null);
74
+ return;
75
+ }
76
+ let turn = turnOf(visit, name);
77
+ if (!document.dispatchEvent(
78
+ new CustomEvent("pageturn:leaving", { cancelable: !0, detail: turn })
79
+ )) {
80
+ native?.c(null);
81
+ return;
82
+ }
83
+ if (name === registry.settings.defaultMove && !registry.b.get(name)?.leave && native?.v(name, to))
84
+ return;
85
+ event.preventDefault();
86
+ let playing = { turn };
87
+ current = playing, leave(turn, (played) => {
88
+ playing.turn = played;
89
+ }).then((handover) => depart(playing, handover));
90
+ }
91
+ function depart(playing, handover) {
92
+ if (current !== playing || playing.handover !== void 0) return;
93
+ let { turn } = playing;
94
+ playing.handover = handover, handover && handover.arriveAs !== null && store.h(
95
+ {
96
+ move: handover.arriveAs ?? turn.move,
97
+ data: handover.data ?? null,
98
+ from: location.pathname,
99
+ at: Date.now()
100
+ },
101
+ turn.to
102
+ ), playing.entry = handover?.returnAs ? {
103
+ move: turn.move,
104
+ to: turn.to.pathname,
105
+ returnAs: handover.returnAs,
106
+ remember: handover.remember ?? null
107
+ } : null, store.i(playing.entry), registry.a?.c(null), location.assign(turn.to.href), stuckTimer = setTimeout(
108
+ () => endWithoutDeparture(playing, "stuck"),
109
+ registry.settings.stuckTimeout
110
+ );
111
+ }
112
+ function endWithoutDeparture(playing, reason) {
113
+ if (current !== playing) return;
114
+ current = null, clearTimeout(stuckTimer), registry.a?.c(void 0), delete document.documentElement.dataset.turnLeaving;
115
+ let stay = { reason, handover: playing.handover ?? null };
116
+ try {
117
+ registry.b.get(playing.turn.move)?.stay?.(playing.turn, stay);
118
+ } catch (error) {
119
+ reportError(error);
120
+ }
121
+ document.dispatchEvent(
122
+ new CustomEvent("pageturn:stayed", {
123
+ detail: { turn: playing.turn, stay }
124
+ })
125
+ );
126
+ }
127
+ function pageHidden() {
128
+ clearTimeout(stuckTimer), stopFollowing();
129
+ let { e: report } = registry;
130
+ if (!report && !window.pageturn?.arrival) return;
131
+ let data = null;
132
+ try {
133
+ data = report?.({ decline: () => registry.f }) ?? null;
134
+ } catch (error) {
135
+ reportError(error);
136
+ }
137
+ let isNone = data === registry.f;
138
+ store.p({
139
+ from: location.pathname,
140
+ at: Date.now(),
141
+ data: isNone ? null : data,
142
+ ...isNone ? { none: !0 } : {}
143
+ });
144
+ }
145
+ function pageShown(event) {
146
+ if (!event.persisted) return;
147
+ registry.a?.c(void 0);
148
+ let playing = current, published = window.pageturn;
149
+ if (!published) return;
150
+ let entry = playing ? playing.entry ?? null : store.r(), arrival2 = store.s(entry, store.o());
151
+ published.arrival = arrival2, arrival2 && (published.t?.(arrival2), published.k?.skipTransition(), followArrival()), playing && endWithoutDeparture(playing, "returned"), arrival2 && tellArrival(arrival2);
152
+ }
153
+
154
+ // src/kernel/core.ts
155
+ function defineMove(name, move) {
156
+ let stored = move;
157
+ registry.b.set(name, stored);
158
+ let reached = inBrowser ? window.pageturn?.arrival : null;
159
+ return reached?.move === name && stored.arrive && queueMicrotask(() => {
160
+ registry.b.get(name) === stored && tellArrival(reached);
161
+ }), () => {
162
+ registry.b.get(name) === stored && registry.b.delete(name);
163
+ };
164
+ }
165
+ function chooseMove(chooser) {
166
+ let stored = chooser;
167
+ return registry.d.push(stored), () => {
168
+ let index = registry.d.indexOf(stored);
169
+ index >= 0 && registry.d.splice(index, 1);
170
+ };
171
+ }
172
+ function arrival() {
173
+ return inBrowser && window.pageturn?.arrival || null;
174
+ }
175
+ function endArrival(move) {
176
+ inBrowser && window.pageturn?.endArrival(move);
177
+ }
178
+ function whenLeft(report) {
179
+ let stored = report;
180
+ return registry.e = stored, () => {
181
+ registry.e === stored && (registry.e = null);
182
+ };
183
+ }
184
+ function configure(settings) {
185
+ for (let [key, value] of Object.entries(settings))
186
+ value !== void 0 && Object.assign(registry.settings, { [key]: value });
187
+ }
188
+ install();
189
+
190
+ export {
191
+ defineMove,
192
+ chooseMove,
193
+ arrival,
194
+ endArrival,
195
+ whenLeft,
196
+ configure
197
+ };
@@ -0,0 +1,33 @@
1
+ // src/common/page.ts
2
+ var inBrowser = typeof document < "u", navigationEntry = () => performance.getEntriesByType("navigation")[0];
3
+ function afterDomContentLoaded(run) {
4
+ let entry = navigationEntry();
5
+ (entry ? entry.domContentLoadedEventStart > 0 : document.readyState !== "loading") ? run() : document.addEventListener("DOMContentLoaded", run, { once: !0 });
6
+ }
7
+ var reduced;
8
+ function reducesMotion() {
9
+ return reduced ??= matchMedia("(prefers-reduced-motion: reduce)"), reduced.matches;
10
+ }
11
+ function notShownAnew() {
12
+ window.pageturn && (window.pageturn.l = !0), delete document.documentElement.dataset.turnEntering;
13
+ }
14
+ var FOCUS_BACK = /* @__PURE__ */ Symbol.for("pageturn.focusBack"), shared = globalThis;
15
+ function giveFocusTo(element, options) {
16
+ shared[FOCUS_BACK] = element;
17
+ try {
18
+ element.focus(options);
19
+ } finally {
20
+ delete shared[FOCUS_BACK];
21
+ }
22
+ }
23
+ var isFocusGivenBack = (element) => shared[FOCUS_BACK] === element;
24
+
25
+ export {
26
+ inBrowser,
27
+ navigationEntry,
28
+ afterDomContentLoaded,
29
+ reducesMotion,
30
+ notShownAnew,
31
+ giveFocusTo,
32
+ isFocusGivenBack
33
+ };
@@ -0,0 +1,167 @@
1
+ import {
2
+ addressOf,
3
+ choose,
4
+ followArrival,
5
+ leave,
6
+ readSession,
7
+ returnArrival,
8
+ tellArrival,
9
+ turnOf,
10
+ writeSession
11
+ } from "./chunk-JHM7F4CH.js";
12
+ import {
13
+ inBrowser,
14
+ reducesMotion
15
+ } from "./chunk-AEOAXMU3.js";
16
+ import {
17
+ registry
18
+ } from "./chunk-YAL4MNCU.js";
19
+
20
+ // src/router/router.ts
21
+ var ENTRIES_KEY = "pageturn:router:entries", KEPT_ENTRIES = 50, CLICK_FRESH_MS = 1e3, INERT = {
22
+ left: Promise.resolve(),
23
+ swapped() {
24
+ },
25
+ cancelled() {
26
+ }
27
+ }, current = null, shown = inBrowser ? location.href : "", lastClick = null;
28
+ inBrowser && (registry.w = !0, addEventListener(
29
+ "click",
30
+ (event) => {
31
+ let link = event.composedPath().find((target) => target instanceof HTMLAnchorElement);
32
+ link && (lastClick = { link, at: Date.now() });
33
+ },
34
+ !0
35
+ ));
36
+ function keyOf(url) {
37
+ let api = window.navigation, here = api?.currentEntry;
38
+ for (let entry of here ? [here, api.entries()[here.index - 1]] : [])
39
+ if (entry?.url && addressOf(new URL(entry.url)) === addressOf(url))
40
+ return entry.key;
41
+ return addressOf(url);
42
+ }
43
+ var readEntries = () => readSession(ENTRIES_KEY) ?? {};
44
+ function writeEntry(key, entry) {
45
+ let { [key]: _, ...entries } = readEntries(), kept = Object.entries(entries).slice(1 - KEPT_ENTRIES);
46
+ writeSession(ENTRIES_KEY, {
47
+ ...Object.fromEntries(kept),
48
+ ...entry ? { [key]: entry } : {}
49
+ });
50
+ }
51
+ function reportOf(from) {
52
+ let { e: report } = registry;
53
+ if (!report && !window.pageturn?.arrival) return null;
54
+ let data = null;
55
+ try {
56
+ data = report?.({ decline: () => registry.f }) ?? null;
57
+ } catch (error) {
58
+ reportError(error);
59
+ }
60
+ let isNone = data === registry.f;
61
+ return {
62
+ from: from.pathname,
63
+ at: Date.now(),
64
+ data: isNone ? null : data,
65
+ ...isNone ? { none: !0 } : {}
66
+ };
67
+ }
68
+ function clickedTo(to) {
69
+ let click = lastClick;
70
+ return click && Date.now() - click.at < CLICK_FRESH_MS && addressOf(new URL(click.link.href)) === addressOf(to) ? click.link : null;
71
+ }
72
+ function startNavigation(start) {
73
+ if (!inBrowser) return INERT;
74
+ let to = new URL(start.to, location.href);
75
+ if (!window.pageturn || window.top !== window || reducesMotion() || // The same path and query: a place in the page (#…), not another page.
76
+ addressOf(to) === addressOf(new URL(shown)))
77
+ return shown = to.href, INERT;
78
+ current && end(current, "superseded");
79
+ let from = new URL(shown), playing = {
80
+ via: start.via,
81
+ to,
82
+ from,
83
+ leftKey: start.via === "history" ? "" : keyOf(from),
84
+ e: reportOf(from)
85
+ };
86
+ current = playing;
87
+ let drive = {
88
+ left: Promise.resolve(),
89
+ swapped: () => swapped(playing),
90
+ cancelled: () => {
91
+ current === playing && end(playing, "cancelled");
92
+ }
93
+ };
94
+ if (start.via === "history") return drive;
95
+ let clicked = start.via === "link" ? start.link ?? clickedTo(to) : null, visit = {
96
+ link: clicked ?? Object.assign(document.createElement("a"), { href: to.href }),
97
+ via: clicked ? "link" : "script",
98
+ from,
99
+ to,
100
+ canHandOver: !0
101
+ }, name = start.move !== void 0 ? start.move : clicked ? choose(visit) : registry.settings.defaultMove;
102
+ if (name === null) return drive;
103
+ let turn = turnOf(visit, name);
104
+ return document.dispatchEvent(
105
+ new CustomEvent("pageturn:leaving", { cancelable: !0, detail: turn })
106
+ ) ? (playing.turn = turn, {
107
+ ...drive,
108
+ left: leave(turn, (played) => {
109
+ playing.turn = played;
110
+ }).then((handover) => {
111
+ current === playing && (playing.handover = handover);
112
+ })
113
+ }) : drive;
114
+ }
115
+ function swapped(playing) {
116
+ if (current !== playing) return;
117
+ current = null;
118
+ let reached = new URL(location.href);
119
+ shown = reached.href;
120
+ let published = window.pageturn;
121
+ if (!published) return;
122
+ published.endArrival(), delete document.documentElement.dataset.turnLeaving;
123
+ let arrival = null;
124
+ if (playing.via === "history")
125
+ arrival = returnArrival(
126
+ readEntries()[keyOf(reached)] ?? null,
127
+ playing.e
128
+ );
129
+ else {
130
+ let { turn, handover } = playing;
131
+ turn && handover && handover.arriveAs !== null && (arrival = {
132
+ move: handover.arriveAs ?? turn.move,
133
+ via: "link",
134
+ from: playing.from.pathname,
135
+ data: handover.data ?? null,
136
+ remembered: null,
137
+ reported: null
138
+ }), writeEntry(
139
+ playing.leftKey,
140
+ turn && handover?.returnAs ? {
141
+ move: turn.move,
142
+ to: reached.pathname,
143
+ returnAs: handover.returnAs,
144
+ remember: handover.remember ?? null
145
+ } : null
146
+ ), document.documentElement.dataset.turnEntering = "";
147
+ }
148
+ published.arrival = arrival, arrival && published.t?.(arrival), followArrival(), arrival && tellArrival(arrival);
149
+ }
150
+ function end(playing, reason) {
151
+ current = null, delete document.documentElement.dataset.turnLeaving;
152
+ let { turn } = playing;
153
+ if (!turn) return;
154
+ let stay = { reason, handover: playing.handover ?? null };
155
+ try {
156
+ registry.b.get(turn.move)?.stay?.(turn, stay);
157
+ } catch (error) {
158
+ reportError(error);
159
+ }
160
+ document.dispatchEvent(
161
+ new CustomEvent("pageturn:stayed", { detail: { turn, stay } })
162
+ );
163
+ }
164
+
165
+ export {
166
+ startNavigation
167
+ };