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.
- package/CHANGELOG.md +35 -0
- package/README.md +174 -4
- package/dist/astro.js +0 -2
- package/dist/chunks/chunk-3ABPYO7H.js +43 -0
- package/dist/chunks/chunk-6FXXBNAF.js +197 -0
- package/dist/chunks/chunk-AEOAXMU3.js +33 -0
- package/dist/chunks/chunk-BOM5VXA5.js +167 -0
- package/dist/chunks/chunk-C3T7BGNG.js +30 -0
- package/dist/chunks/chunk-FYSSNYA4.js +17 -0
- package/dist/chunks/chunk-JHM7F4CH.js +172 -0
- package/dist/chunks/chunk-LM3XLSCW.js +799 -0
- package/dist/chunks/{chunk-QJ7GGFM4.js → chunk-YAL4MNCU.js} +4 -6
- package/dist/core.js +4 -3
- package/dist/dev/chunks/chunk-3JAWIMHB.js +184 -0
- package/dist/dev/chunks/chunk-3SU4CIKL.js +799 -0
- package/dist/dev/chunks/chunk-AEOAXMU3.js +33 -0
- package/dist/dev/chunks/chunk-C3T7BGNG.js +30 -0
- package/dist/dev/chunks/chunk-FYSSNYA4.js +17 -0
- package/dist/dev/chunks/chunk-I4SBUABE.js +175 -0
- package/dist/dev/chunks/chunk-U3XN54YY.js +219 -0
- package/dist/dev/chunks/chunk-Y6TD2T53.js +43 -0
- package/dist/dev/chunks/chunk-YAL4MNCU.js +22 -0
- package/dist/dev/core.js +19 -0
- package/dist/dev/index.js +21 -0
- package/dist/dev/morph-markup.js +34 -0
- package/dist/dev/morph.js +457 -0
- package/dist/dev/native.js +3 -0
- package/dist/dev/next.js +79 -0
- package/dist/dev/nuxt-plugin.js +24 -0
- package/dist/dev/prefetch.js +2 -0
- package/dist/dev/react-router.js +46 -0
- package/dist/dev/router.js +9 -0
- package/dist/dev/sveltekit.js +32 -0
- package/dist/dev/testing.js +48 -0
- package/dist/dev/turbo.js +49 -0
- package/dist/dev/vue.js +11 -0
- package/dist/head-core.js +1 -1
- package/dist/head-string.js +6 -6
- package/dist/head.js +1 -1
- package/dist/index.js +6 -5
- package/dist/morph-head.js +1 -1
- package/dist/morph-markup.js +11 -9
- package/dist/morph.js +145 -287
- package/dist/native.js +3 -2
- package/dist/next.js +79 -0
- package/dist/nuxt-plugin.js +24 -0
- package/dist/nuxt.js +34 -0
- package/dist/pageturn-morph.js +1 -1
- package/dist/pageturn.js +1 -1
- package/dist/prefetch.js +2 -2
- package/dist/react-router.js +46 -0
- package/dist/router.js +5 -183
- package/dist/sveltekit-server.js +22 -0
- package/dist/sveltekit.js +32 -0
- package/dist/testing.js +12 -22
- package/dist/turbo.js +49 -0
- package/dist/types/astro/integration.d.ts +0 -4
- package/dist/types/common/dev.d.ts +6 -0
- package/dist/types/common/page.d.ts +5 -0
- package/dist/types/common/registry.d.ts +3 -1
- package/dist/types/next/next.d.ts +27 -0
- package/dist/types/nuxt/module.d.ts +32 -0
- package/dist/types/react-router/react-router.d.ts +12 -0
- package/dist/types/router/router.d.ts +4 -2
- package/dist/types/sveltekit/server.d.ts +21 -0
- package/dist/types/sveltekit/sveltekit.d.ts +32 -0
- package/dist/types/turbo/turbo.d.ts +1 -0
- package/dist/types/vue/vue.d.ts +36 -0
- package/dist/vue.js +11 -0
- package/package.json +71 -4
- package/dist/chunks/chunk-237GNQNZ.js +0 -37
- package/dist/chunks/chunk-7LMNJ25V.js +0 -244
- package/dist/chunks/chunk-QC36TQP3.js +0 -24
- package/dist/chunks/chunk-WTHN3UIQ.js +0 -1000
- 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?**
|
|
728
|
-
|
|
729
|
-
|
|
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
|
@@ -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
|
+
};
|