pageturn 0.1.0 → 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 +41 -0
  2. package/README.md +179 -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 +102 -23
  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
@@ -1,1000 +0,0 @@
1
- import {
2
- arrival,
3
- defineMove
4
- } from "./chunk-7LMNJ25V.js";
5
- import {
6
- afterDomContentLoaded,
7
- giveFocusTo,
8
- navigationEntry,
9
- notShownAnew,
10
- readSession,
11
- reducesMotion,
12
- removeSession,
13
- writeSession
14
- } from "./chunk-YBAP3PHO.js";
15
- import {
16
- isDeclined,
17
- registry
18
- } from "./chunk-QJ7GGFM4.js";
19
-
20
- // src/morph/end.ts
21
- var wait = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
22
- var within = (promise, milliseconds) => Promise.race([promise, wait(milliseconds).then(() => null)]);
23
- var isEnding = (animation) => Number.isFinite(animation.effect?.getComputedTiming().endTime);
24
- var CORNERS = ["TopLeft", "TopRight", "BottomRight", "BottomLeft"];
25
- var cornersOf = (radius) => typeof radius === "number" ? [radius, radius, radius, radius] : radius;
26
- var radiusOf = (corners) => corners.every((corner) => corner === corners[0]) ? corners[0] : corners;
27
- function ownCorners(style, width, scale = 1) {
28
- return radiusOf(
29
- CORNERS.map((corner) => {
30
- const value = style[`border${corner}Radius`];
31
- const radius = Number.parseFloat(value) || 0;
32
- return value.includes("%") ? radius / 100 * width : radius * scale;
33
- })
34
- );
35
- }
36
- var cornersCss = (radius, scale = 1) => cornersOf(radius).map((corner) => `${corner / scale}px`).join(" ");
37
- function seenCorners(element, box) {
38
- const corners = [
39
- ...cornersOf(ownCorners(getComputedStyle(element), box.width))
40
- ];
41
- const near = (one, other) => Math.abs(one - other) < 1;
42
- for (let clip = element.parentElement; clip && clip !== document.body; clip = clip.parentElement) {
43
- const style = getComputedStyle(clip);
44
- if (style.overflow === "visible" && !/paint|strict|content/.test(style.contain)) {
45
- continue;
46
- }
47
- const edge = clip.getBoundingClientRect();
48
- const left = edge.left + clip.clientLeft;
49
- const top = edge.top + clip.clientTop;
50
- const right = near(box.right, left + clip.clientWidth);
51
- const bottom = near(box.bottom, top + clip.clientHeight);
52
- const atLeft = near(box.left, left);
53
- const atTop = near(box.top, top);
54
- const radii = cornersOf(ownCorners(style, edge.width));
55
- const border = Math.max(clip.clientLeft, clip.clientTop);
56
- [
57
- atLeft && atTop,
58
- right && atTop,
59
- right && bottom,
60
- atLeft && bottom
61
- ].forEach((meets, index) => {
62
- if (meets) {
63
- corners[index] = Math.max(
64
- corners[index] ?? 0,
65
- (radii[index] ?? 0) - border
66
- );
67
- }
68
- });
69
- }
70
- return radiusOf(corners);
71
- }
72
- function endOf(element) {
73
- const rect = element.getBoundingClientRect();
74
- const { left, top, width, height } = rect;
75
- const end = {
76
- box: { left, top, width, height },
77
- radius: seenCorners(element, rect)
78
- };
79
- const picture2 = element.matches("img") ? element : element.querySelector("img");
80
- if (picture2?.sizes) end.sizes = picture2.sizes;
81
- return end;
82
- }
83
- var shifted = (end, x, y) => ({
84
- ...end,
85
- box: { ...end.box, left: end.box.left + x, top: end.box.top + y }
86
- });
87
- var looksSame = (one, other) => {
88
- const drawn = (end) => [
89
- end.box.left,
90
- end.box.top,
91
- end.box.width,
92
- end.box.height,
93
- ...cornersOf(end.radius),
94
- end.rotate ?? 0,
95
- end.text?.box.left ?? 0,
96
- end.text?.box.top ?? 0,
97
- Number.parseFloat(end.text?.type.fontSize ?? "0")
98
- ];
99
- const others = drawn(other);
100
- return drawn(one).every(
101
- (value, index) => Math.abs(value - (others[index] ?? 0)) < 0.5
102
- ) && one.text?.color === other.text?.color && one.background === other.background;
103
- };
104
- function atTheirEnd(animations, read) {
105
- const held = animations.filter(isEnding);
106
- const times = held.map((animation) => animation.currentTime);
107
- for (const animation of held) {
108
- animation.currentTime = animation.effect?.getComputedTiming().endTime;
109
- }
110
- try {
111
- return read();
112
- } finally {
113
- held.forEach((animation, index) => {
114
- animation.currentTime = times[index] ?? null;
115
- });
116
- }
117
- }
118
-
119
- // src/morph/layer.ts
120
- var LAYER_STYLE = "position:fixed;inset:auto;left:0;top:0;margin:0;border:0;padding:0;overflow:visible;background:none;color:inherit;pointer-events:none;z-index:2147483647;transform-origin:50% 50%;max-width:none;max-height:none";
121
- var CONTENT_STYLE = "margin:0;max-width:none;max-height:none;box-sizing:border-box;visibility:visible;translate:none;rotate:none;scale:none;transform:none";
122
- function cloned(element) {
123
- const copy = element.cloneNode(true);
124
- for (const named of [copy, ...copy.querySelectorAll("[id]")]) {
125
- named.removeAttribute("id");
126
- }
127
- return copy;
128
- }
129
- var sizeOf = (layer) => [
130
- Number.parseFloat(layer.style.width),
131
- Number.parseFloat(layer.style.height)
132
- ];
133
- var centredOn = (box, width, height) => `${box.left + (box.width - width) / 2}px ${box.top + (box.height - height) / 2}px`;
134
- var moveAt = (pose, end) => ({
135
- ...pose.move,
136
- opacity: end.opacity ?? 1
137
- });
138
- function standAt(layer, kind, end) {
139
- const pose = kind.pose(layer)(end);
140
- Object.assign(layer.style, moveAt(pose, end));
141
- Object.assign(pose.on.style, pose.look);
142
- if (pose.inner) Object.assign(pose.inner.on.style, pose.inner.look);
143
- }
144
- function markup(layer) {
145
- const copy = layer.cloneNode(true);
146
- for (const shot of copy.querySelectorAll("[data-turn-shot]")) shot.remove();
147
- const drawn = layer.querySelectorAll("img");
148
- copy.querySelectorAll("img").forEach((picture2, index) => {
149
- const live = drawn[index];
150
- const file = live?.complete && live.naturalWidth ? live.currentSrc : picture2.dataset.turnFile;
151
- if (!file) return;
152
- picture2.src = file;
153
- picture2.removeAttribute("srcset");
154
- picture2.removeAttribute("sizes");
155
- for (const source of picture2.parentElement?.querySelectorAll("source") ?? []) {
156
- source.remove();
157
- }
158
- });
159
- for (const styled of [
160
- copy,
161
- ...copy.querySelectorAll("[style]")
162
- ]) {
163
- styled.dataset.turnStyle = styled.style.cssText;
164
- styled.removeAttribute("style");
165
- }
166
- return copy.outerHTML;
167
- }
168
-
169
- // src/morph/picture.ts
170
- var PICTURE_WAIT_MS = 250;
171
- var MEDIA = "img,video,canvas";
172
- async function whenDrawn(element) {
173
- const picture2 = element.matches("img") ? element : element.querySelector("img");
174
- if (picture2 instanceof HTMLImageElement) {
175
- await within(picture2.decode(), PICTURE_WAIT_MS).catch(() => {
176
- });
177
- }
178
- }
179
- function snapshot(shown, width, height, position) {
180
- const { naturalWidth, naturalHeight } = shown;
181
- if (!shown.complete || !naturalWidth || !naturalHeight) return null;
182
- const canvas = document.createElement("canvas");
183
- const cover = Math.max(width / naturalWidth, height / naturalHeight);
184
- const across = width / cover;
185
- const down = height / cover;
186
- const [x = 0.5, y = 0.5] = position.split(" ").map(
187
- (share) => share.endsWith("%") ? Number.parseFloat(share) / 100 : 0.5
188
- );
189
- canvas.width = Math.round(across);
190
- canvas.height = Math.round(down);
191
- try {
192
- canvas.getContext("2d")?.drawImage(
193
- shown,
194
- (naturalWidth - across) * x,
195
- (naturalHeight - down) * y,
196
- across,
197
- down,
198
- 0,
199
- 0,
200
- canvas.width,
201
- canvas.height
202
- );
203
- } catch {
204
- return null;
205
- }
206
- canvas.dataset.turnShot = "";
207
- canvas.style.cssText = "position:absolute;left:0;top:0;width:100%;height:100%;z-index:-1";
208
- return canvas;
209
- }
210
- function pictureOf(element) {
211
- const inside = element.querySelectorAll(MEDIA);
212
- return element.matches(MEDIA) ? element : inside.length === 1 ? inside[0] : null;
213
- }
214
- var picture = {
215
- read(element, end) {
216
- const inner = pictureOf(element);
217
- if (!inner || inner === element) return;
218
- const style = getComputedStyle(inner);
219
- const own = new DOMMatrix(style.transform);
220
- const zoom = Math.hypot(own.a, own.b) * (Number.parseFloat(style.scale) || 1);
221
- if (Math.abs(zoom - 1) > 1e-3) end.zoom = zoom;
222
- },
223
- build(element, from, to) {
224
- const layer = document.createElement("div");
225
- const media = pictureOf(element);
226
- const content = cloned(
227
- media ? media.closest("picture") ?? media : element
228
- );
229
- const drawn = content.matches(MEDIA) ? content : (media && content.querySelector(MEDIA)) ?? content;
230
- if (drawn !== content) content.style.cssText = "display:contents";
231
- content.dataset.turnContent = "";
232
- drawn.style.cssText += `;display:block;width:100%;height:100%;${CONTENT_STYLE}`;
233
- if (media) {
234
- drawn.style.objectFit = "cover";
235
- drawn.style.objectPosition = getComputedStyle(media).objectPosition;
236
- }
237
- const { width, height } = (to ?? from).box;
238
- layer.style.cssText = `${LAYER_STYLE};width:${width}px;height:${height}px`;
239
- layer.append(content);
240
- if (drawn instanceof HTMLImageElement && media instanceof HTMLImageElement) {
241
- drawn.loading = "eager";
242
- drawn.decoding = "sync";
243
- drawn.dataset.turnFile = media.currentSrc;
244
- if (to) {
245
- const sizes = to.sizes ?? `${Math.ceil(width)}px`;
246
- for (const sized of layer.querySelectorAll("img[srcset],source[srcset]")) {
247
- sized.sizes = sizes;
248
- }
249
- }
250
- const shot = snapshot(media, width, height, drawn.style.objectPosition);
251
- if (shot) {
252
- layer.append(shot);
253
- const drew = () => {
254
- drawn.decode().then(
255
- () => shot.remove(),
256
- () => {
257
- }
258
- );
259
- };
260
- if (drawn.complete && drawn.naturalWidth) drew();
261
- else drawn.addEventListener("load", drew, { once: true });
262
- }
263
- }
264
- return layer;
265
- },
266
- pose(layer) {
267
- const [width, height] = sizeOf(layer);
268
- const held = layer.querySelector(MEDIA) ?? layer.firstElementChild;
269
- return ({ box, rotate = 0, radius, zoom = 1 }) => {
270
- const cover = Math.max(box.width / width, box.height / height);
271
- return {
272
- move: {
273
- translate: centredOn(box, width, height),
274
- rotate: `${rotate}deg`,
275
- scale: `${cover}`
276
- },
277
- look: {
278
- clipPath: `inset(${(height - box.height / cover) / 2}px ${(width - box.width / cover) / 2}px round ${cornersCss(radius, cover)})`
279
- },
280
- on: layer,
281
- inner: { look: { scale: `${zoom}` }, on: held }
282
- };
283
- };
284
- },
285
- fit(layer, to) {
286
- layer.style.width = `${to.box.width}px`;
287
- layer.style.height = `${to.box.height}px`;
288
- }
289
- };
290
-
291
- // src/morph/place.ts
292
- var MORPH_KEY = "pageturn:morph";
293
- var outsideLayer = (selector) => `:is(${selector}):not([data-turn-layer] *)`;
294
- var onPage = (selector) => document.querySelector(outsideLayer(selector));
295
- function whenParsed(run) {
296
- if (document.readyState === "loading") {
297
- document.addEventListener("readystatechange", run, { once: true });
298
- } else {
299
- run();
300
- }
301
- }
302
- function putBack(selector, name, reopens = false, kept = [], link) {
303
- const root = document.documentElement;
304
- root.dataset.turnRestoring = "";
305
- notShownAnew();
306
- const around = [];
307
- whenParsed(() => {
308
- let origin = null;
309
- try {
310
- origin = selector ? onPage(selector) : null;
311
- } catch {
312
- }
313
- origin?.setAttribute("data-turn-origin", name);
314
- for (const [where, attribute, value] of kept) {
315
- try {
316
- const element = document.querySelector(where);
317
- if (value === null) element?.removeAttribute(attribute);
318
- else element?.setAttribute(attribute, value);
319
- } catch {
320
- }
321
- }
322
- for (let within2 = origin?.parentElement?.closest("[popover]"); reopens && within2; within2 = within2.parentElement?.closest("[popover]")) {
323
- around.unshift(within2);
324
- }
325
- for (const popover of around) {
326
- try {
327
- popover.showPopover();
328
- } catch {
329
- }
330
- }
331
- });
332
- afterDomContentLoaded(
333
- () => requestAnimationFrame(
334
- () => requestAnimationFrame(() => {
335
- if (link) giveFocusBack(link, around);
336
- delete root.dataset.turnRestoring;
337
- })
338
- )
339
- );
340
- }
341
- var linkAt = (address, rank) => [...document.querySelectorAll("a[href]")].filter(
342
- (anchor) => anchor.href === address
343
- )[rank];
344
- function giveFocusBack([address, rank, isShown], around) {
345
- const focused = document.activeElement;
346
- const target = linkAt(address, rank);
347
- if (target && (!focused || focused === document.body || around.some((popover) => popover.contains(focused)))) {
348
- giveFocusTo(target, {
349
- preventScroll: true,
350
- focusVisible: isShown
351
- });
352
- }
353
- }
354
- function showLayer(layer) {
355
- layer.setAttribute("aria-hidden", "true");
356
- layer.setAttribute("popover", "manual");
357
- layer.dataset.turnLayer = "";
358
- document.documentElement.append(layer);
359
- try {
360
- layer.showPopover();
361
- } catch {
362
- }
363
- }
364
- function placeHandedLayer() {
365
- if (window.top !== window) {
366
- try {
367
- const fromName = JSON.parse(
368
- window.name.split(`${MORPH_KEY}:`)[1] ?? ""
369
- );
370
- whenParsed(() => scrollTo(fromName.x, fromName.y));
371
- putBack(fromName.p, fromName.o, fromName.v, fromName.w);
372
- if (window.pageturn) {
373
- window.pageturn.behind = { move: fromName.m, data: fromName.d };
374
- }
375
- } catch {
376
- }
377
- return null;
378
- }
379
- const flight = readSession(MORPH_KEY);
380
- removeSession(MORPH_KEY);
381
- if (!flight || flight.n !== window.pageturn?.arrival?.move) return null;
382
- const holder = document.createElement("div");
383
- holder.innerHTML = flight.h;
384
- const layer = holder.firstElementChild;
385
- if (!(layer instanceof HTMLElement)) return null;
386
- let sheet = null;
387
- if (flight.s) {
388
- try {
389
- const rule = new CSSStyleSheet();
390
- rule.insertRule(`${outsideLayer(flight.s)}{visibility:hidden!important}`);
391
- document.adoptedStyleSheets = [...document.adoptedStyleSheets, rule];
392
- sheet = rule;
393
- } catch {
394
- }
395
- }
396
- for (const styled of [
397
- layer,
398
- ...layer.querySelectorAll("[data-turn-style]")
399
- ]) {
400
- styled.style.cssText = styled.dataset.turnStyle ?? "";
401
- }
402
- showLayer(layer);
403
- const placed = { flight, layer, sheet, taken: false };
404
- if (flight.b) document.documentElement.dataset.turnBehind = "";
405
- const { x = 0, y } = flight;
406
- if (y !== void 0) whenParsed(() => scrollTo(x, y));
407
- if (flight.o) putBack(flight.s, flight.o, flight.v, flight.w, flight.l);
408
- afterDomContentLoaded(
409
- () => setTimeout(() => placed.taken || clearLayer(placed))
410
- );
411
- return placed;
412
- }
413
- function clearLayer(placed) {
414
- placed.taken = true;
415
- document.querySelector("[data-turn-origin]")?.removeAttribute("data-turn-origin");
416
- placed.layer.remove();
417
- document.adoptedStyleSheets = document.adoptedStyleSheets.filter(
418
- (sheet) => sheet !== placed.sheet
419
- );
420
- }
421
-
422
- // src/morph/timing.ts
423
- var over = (timing, leg = {}) => ({
424
- ...timing,
425
- duration: leg.duration ?? timing.duration,
426
- delay: leg.delay ?? timing.delay,
427
- easing: leg.easing ?? timing.easing
428
- });
429
-
430
- // src/morph/define.ts
431
- var DURATION_MS = 500;
432
- var EASING = "cubic-bezier(0.2, 0, 0, 1)";
433
- var CORRECTION_MS = 300;
434
- function handedOver() {
435
- if (window.pageturnMorph === void 0)
436
- window.pageturnMorph = placeHandedLayer();
437
- return window.pageturnMorph;
438
- }
439
- var isHandingOver = false;
440
- var isLeaving = () => isHandingOver;
441
- function hide(element) {
442
- const hidden = element;
443
- hidden.style.visibility = "hidden";
444
- return hidden;
445
- }
446
- var defined = /* @__PURE__ */ new Set();
447
- var isDefined = (name) => defined.has(name);
448
- function defineMorph(name, morph) {
449
- if (window.top !== window || reducesMotion()) {
450
- return () => {
451
- };
452
- }
453
- defined.add(name);
454
- const kind = morph.as ?? picture;
455
- const finders = [morph.find ?? []].flat();
456
- const atRest = (element) => (
457
- // An element of another document (a page peek() loaded) has none of this one's.
458
- element.ownerDocument !== document ? stage.read(element) : atTheirEnd(
459
- document.getAnimations().filter((animation) => {
460
- const { effect } = animation;
461
- return effect instanceof KeyframeEffect && effect.target?.contains(element);
462
- }),
463
- () => stage.read(element)
464
- )
465
- );
466
- const stage = {
467
- name,
468
- morph,
469
- live: null,
470
- endOf,
471
- onLeave: [],
472
- onStay: [],
473
- onPlay: [],
474
- onRemove: [],
475
- siteAnimations: [],
476
- atRest,
477
- read(element) {
478
- const end = stage.endOf(element);
479
- kind.read?.(element, end, element.matches(morph.to));
480
- return end;
481
- },
482
- timing: (leg) => over(
483
- over(
484
- { duration: DURATION_MS, delay: 0, easing: EASING, fill: "both" },
485
- morph
486
- ),
487
- leg
488
- ),
489
- about: (link, back2) => ({
490
- back: back2,
491
- link,
492
- decline: () => registry.f,
493
- measure(element, at) {
494
- const end = stage.read(element);
495
- if (!at) return end;
496
- const place = stage.read(at);
497
- const own = end.text?.box ?? end.box;
498
- const other = place.text?.box ?? place.box;
499
- const across = other.left - own.left;
500
- const down = other.top - own.top;
501
- const there = shifted(end, across, down);
502
- if (there.text) {
503
- there.text = {
504
- ...there.text,
505
- box: {
506
- ...there.text.box,
507
- left: there.text.box.left + across,
508
- top: there.text.box.top + down
509
- }
510
- };
511
- }
512
- return there;
513
- },
514
- scroll: { x: scrollX, y: scrollY }
515
- }),
516
- layer(element, at, to) {
517
- const layer = kind.build(element, at, to);
518
- standAt(layer, kind, at);
519
- return layer;
520
- },
521
- async put(element, at, to) {
522
- const layer = stage.layer(element, at, to);
523
- stage.live = { layer, hidden: hide(element) };
524
- showLayer(layer);
525
- return layer;
526
- },
527
- async travel(start, stop, about, timing, plays = true, behind = null) {
528
- const layer = stage.live?.layer;
529
- if (!layer) return;
530
- const pose = kind.pose(layer);
531
- const first = pose(start);
532
- const last = pose(stop);
533
- layer.animate([moveAt(first, start), moveAt(last, stop)], timing);
534
- first.on.animate([first.look, last.look], timing);
535
- if (first.inner && last.inner && JSON.stringify(first.inner.look) !== JSON.stringify(last.inner.look)) {
536
- first.inner.on.animate([first.inner.look, last.inner.look], timing);
537
- }
538
- let end = () => {
539
- };
540
- const finished = new Promise((resolve) => {
541
- end = resolve;
542
- });
543
- try {
544
- kind.play?.(layer, start, stop, timing, about.back);
545
- for (const play of stage.onPlay) {
546
- play(layer, start, stop, timing, finished);
547
- }
548
- if (plays) {
549
- morph.during?.({
550
- ...about,
551
- layer,
552
- content: layer.firstElementChild,
553
- box: layer.querySelector("[data-turn-box]"),
554
- words: layer.querySelector("[data-turn-words]"),
555
- animate(element, keyframes, options) {
556
- const animation = element.animate(keyframes, {
557
- ...timing,
558
- ...options
559
- });
560
- stage.siteAnimations.push(animation);
561
- return animation;
562
- },
563
- start,
564
- stop,
565
- timing,
566
- behind,
567
- finished,
568
- atEnd: (read) => atTheirEnd(layer.getAnimations({ subtree: true }), read)
569
- });
570
- }
571
- } catch (error) {
572
- reportError(error);
573
- }
574
- if (behind && behind.getAnimations().length === 0) {
575
- behind.animate({ opacity: [0, 1] }, over(timing, stage.appear));
576
- }
577
- const playing = () => [
578
- ...layer.getAnimations({ subtree: true }),
579
- ...behind?.getAnimations() ?? []
580
- ].filter(isEnding);
581
- await Promise.all(
582
- [...playing(), ...stage.siteAnimations.filter(isEnding)].map(
583
- (animation) => animation.finished
584
- )
585
- ).catch(() => {
586
- });
587
- for (const animation of playing()) {
588
- try {
589
- animation.commitStyles();
590
- } catch {
591
- }
592
- animation.cancel();
593
- }
594
- end();
595
- },
596
- hand(flight) {
597
- isHandingOver = true;
598
- writeSession(MORPH_KEY, flight);
599
- },
600
- async land(placed2, target, told, scroll = { x: scrollX, y: scrollY }, leg) {
601
- const about = { ...told, scroll };
602
- placed2.taken = true;
603
- const { flight, layer } = placed2;
604
- try {
605
- if (document.readyState === "loading") {
606
- await new Promise((parsed) => whenParsed(parsed));
607
- }
608
- const element = target();
609
- if (!element) return;
610
- stage.live = { layer, hidden: hide(element) };
611
- const there = shifted(
612
- atRest(element),
613
- scrollX - scroll.x,
614
- scrollY - scroll.y
615
- );
616
- if (flight.t) {
617
- const ends = about.back ? { from: there, to: flight.f ?? null } : { from: flight.f ?? there, to: there };
618
- if (flight.f) morph.ends?.(ends, about);
619
- const stop = (about.back ? ends.from : ends.to) ?? there;
620
- if (!looksSame(flight.e, stop)) {
621
- await stage.travel(
622
- flight.e,
623
- stop,
624
- about,
625
- { ...stage.timing(leg), delay: 0, duration: CORRECTION_MS },
626
- false
627
- );
628
- }
629
- } else {
630
- const ends = about.back ? { from: there, to: flight.e } : { from: flight.e, to: there };
631
- if (!isDeclined(morph.ends?.(ends, about))) {
632
- const start = (about.back ? ends.to : ends.from) ?? flight.e;
633
- const stop = (about.back ? ends.from : ends.to) ?? there;
634
- kind.fit?.(layer, stop);
635
- await stage.travel(start, stop, about, stage.timing(leg));
636
- }
637
- }
638
- await whenDrawn(element);
639
- } catch (error) {
640
- reportError(error);
641
- } finally {
642
- stage.drop();
643
- clearLayer(placed2);
644
- }
645
- },
646
- drop() {
647
- for (const animation of stage.siteAnimations.splice(0))
648
- animation.cancel();
649
- stage.live?.layer.remove();
650
- stage.live?.hidden.style.removeProperty("visibility");
651
- stage.live = null;
652
- }
653
- };
654
- const remove = defineMove(name, {
655
- match: (visit) => morph.match ? morph.match(visit) : Boolean(morph.from?.(visit.link)),
656
- async leave(turn) {
657
- const element = morph.from?.(turn.link);
658
- if (!element || !turn.canHandOver || stage.live) return turn.decline();
659
- const about = stage.about(turn.link, false);
660
- const ends = {
661
- from: stage.read(element),
662
- to: null
663
- };
664
- for (const finder of finders) {
665
- try {
666
- ends.to ??= await finder(turn.to, morph.to, atRest);
667
- } catch (error) {
668
- reportError(error);
669
- }
670
- }
671
- if (isDeclined(morph.ends?.(ends, about))) return turn.decline();
672
- const { from, to } = ends;
673
- if (from.box.width === 0 || from.box.height === 0) return turn.decline();
674
- const layer = await stage.put(element, from, to);
675
- const leaving = turn.leaving();
676
- if (to) await stage.travel(from, to, about, stage.timing());
677
- await leaving;
678
- stage.hand({
679
- n: morph.arriveAs ?? name,
680
- m: name,
681
- h: markup(layer),
682
- e: to ?? from,
683
- ...to ? { f: from } : {},
684
- t: Boolean(to),
685
- s: morph.to
686
- });
687
- let handover = {
688
- arriveAs: morph.arriveAs ?? name,
689
- data: morph.data?.(turn) ?? null
690
- };
691
- for (const add of stage.onLeave) {
692
- handover = {
693
- ...handover,
694
- ...add(turn, element, from, handover.data)
695
- };
696
- }
697
- return handover;
698
- },
699
- stay(turn, stay) {
700
- isHandingOver = false;
701
- stage.drop();
702
- for (const tell of stage.onStay) tell(stay);
703
- morph.stay?.(turn, stay);
704
- }
705
- });
706
- for (const begin of [
707
- ...finders.map((finder) => finder.start),
708
- ...morph.use ?? []
709
- ]) {
710
- begin?.(stage);
711
- }
712
- const placed = handedOver();
713
- if (placed && !placed.taken && placed.flight.m === name) {
714
- stage.land(placed, () => onPage(morph.to), stage.about(null, false));
715
- }
716
- return () => {
717
- remove();
718
- defined.delete(name);
719
- for (const stop of stage.onRemove) stop();
720
- };
721
- }
722
-
723
- // src/morph/way.ts
724
- var originKey = (name) => `${MORPH_KEY}:from:${name}`;
725
- function originOf(name) {
726
- const from = readSession(originKey(name));
727
- const entry = navigationEntry();
728
- if (from && window.top === window && new URL(from.u).pathname === location.pathname && entry?.type === "navigate" && window.pageturn?.arrival?.move !== from.a) {
729
- forgetWayOut(name);
730
- return null;
731
- }
732
- return from;
733
- }
734
- function forgetWayOut(name) {
735
- if (window.top !== window) return;
736
- removeSession(originKey(name));
737
- }
738
- var IN_VIEW = 0.25;
739
- function shareOnScreen({ left, top, width, height }) {
740
- const across = Math.min(left + width, innerWidth) - Math.max(left, 0);
741
- const down = Math.min(top + height, innerHeight) - Math.max(top, 0);
742
- return Math.max(across, 0) * Math.max(down, 0) / (width * height || 1);
743
- }
744
- function pathOf(element) {
745
- let path = "";
746
- for (let node = element; node !== document.body && node.parentElement; node = node.parentElement) {
747
- const rank = [...node.parentElement.children].indexOf(node) + 1;
748
- path = `>${node.localName}:nth-child(${rank})${path}`;
749
- }
750
- return `body${path}`;
751
- }
752
- function elementNamedByAddress() {
753
- try {
754
- return document.getElementById(decodeURIComponent(location.hash.slice(1)));
755
- } catch {
756
- return null;
757
- }
758
- }
759
- function scrollOfAddress() {
760
- elementNamedByAddress()?.scrollIntoView({ behavior: "instant" });
761
- return { x: scrollX, y: scrollY };
762
- }
763
- function isInAnOpenPopover(element) {
764
- try {
765
- return element.parentElement?.closest(":popover-open") != null;
766
- } catch {
767
- return false;
768
- }
769
- }
770
- var links = () => [
771
- ...document.querySelectorAll("a[href]")
772
- ];
773
- var waysBack = /* @__PURE__ */ new WeakMap();
774
- function readKept(keep) {
775
- const read = [];
776
- for (const [selector, names] of Object.entries(keep)) {
777
- let element = null;
778
- try {
779
- element = document.querySelector(selector);
780
- } catch {
781
- }
782
- if (!element) continue;
783
- for (const attribute of [names].flat()) {
784
- read.push([selector, attribute, element.getAttribute(attribute)]);
785
- }
786
- }
787
- return read;
788
- }
789
- function rankOf(link) {
790
- const { href } = link;
791
- let rank = 0;
792
- for (const other of links()) {
793
- if (other === link) return rank;
794
- if (other.href === href) rank += 1;
795
- }
796
- return -1;
797
- }
798
- var backFields = (from, name) => ({
799
- s: from?.p ?? "",
800
- ...from ? {
801
- ...putBackFields(from, name),
802
- l: [from.u, from.k, !!from.i]
803
- } : {}
804
- });
805
- var putBackFields = (from, name) => ({
806
- o: name,
807
- v: from.v,
808
- ...from.w?.length ? { w: from.w } : {}
809
- });
810
- function wayBackOf(stage) {
811
- const known = waysBack.get(stage);
812
- if (known) return known;
813
- const key = originKey(stage.name);
814
- const way = {
815
- name: `${stage.name}-back`,
816
- origin: () => originOf(stage.name),
817
- keep: {}
818
- };
819
- waysBack.set(stage, way);
820
- stage.onLeave.push((turn, element, from, data) => {
821
- writeSession(key, {
822
- d: data,
823
- a: stage.morph.arriveAs ?? stage.name,
824
- f: location.pathname,
825
- u: turn.link.href,
826
- k: rankOf(turn.link),
827
- p: pathOf(element),
828
- v: isInAnOpenPopover(element),
829
- w: readKept(way.keep),
830
- // A click from the keyboard leaves the link with its ring; a mouse's, without.
831
- i: turn.link.matches(":focus-visible"),
832
- x: scrollX,
833
- y: scrollY,
834
- e: from
835
- });
836
- });
837
- function startedHere() {
838
- const from = way.origin();
839
- return from?.f === location.pathname ? from : null;
840
- }
841
- function landBack(placed, isShown) {
842
- const reached = arrival();
843
- const options = reached?.via === "history" ? way.byHistory : way.byLink;
844
- if (!reached || !options) return;
845
- const from = startedHere();
846
- const leading = links().filter((link2) => link2.pathname === reached.from);
847
- const clicked = from ? linkAt(from.u, from.k) : void 0;
848
- const link = clicked && leading.includes(clicked) ? clicked : leading.find((link2) => stage.morph.from?.(link2));
849
- if (!link) return;
850
- if (link !== clicked) {
851
- document.adoptedStyleSheets = document.adoptedStyleSheets.filter(
852
- (sheet) => sheet !== placed.sheet
853
- );
854
- }
855
- const isByLink = reached.via === "link";
856
- const { x = 0, y } = placed.flight;
857
- if (y !== void 0) scrollTo(x, y);
858
- stage.land(
859
- placed,
860
- () => stage.morph.from?.(link) ?? null,
861
- stage.about(link, true),
862
- y !== void 0 ? { x, y } : from && !isByLink && !isShown ? (
863
- // On a Back the browser brings the scroll back itself, sometimes after this.
864
- { x: from.x, y: from.y }
865
- ) : isByLink ? scrollOfAddress() : { x: scrollX, y: scrollY },
866
- options
867
- );
868
- }
869
- queueMicrotask(() => {
870
- const placed = handedOver();
871
- if (placed && !placed.taken && placed.flight.m === way.name) {
872
- landBack(placed, false);
873
- }
874
- });
875
- stage.onStay.push((stay) => {
876
- if (stay.reason !== "returned" || !arrival()) return;
877
- const placed = placeHandedLayer();
878
- if (placed?.flight.m === way.name) {
879
- landBack(placed, true);
880
- if (!placed.taken) clearLayer(placed);
881
- }
882
- });
883
- return way;
884
- }
885
- function wayOut(name) {
886
- const from = originOf(name);
887
- return from && {
888
- page: from.f,
889
- scroll: { x: from.x, y: from.y },
890
- data: from.d
891
- };
892
- }
893
-
894
- // src/morph/back.ts
895
- function back(options = {}) {
896
- return (stage) => {
897
- const way = wayBackOf(stage);
898
- way.byHistory = options;
899
- Object.assign(way.keep, options.keep);
900
- stage.onLeave.push((_turn, _element, _from, data) => ({
901
- returnAs: way.name,
902
- remember: data
903
- }));
904
- const said = () => {
905
- if (isLeaving()) return;
906
- const from = way.origin();
907
- const element = onPage(stage.morph.to);
908
- const end = element && stage.read(element);
909
- const store = window.pageturn?.i;
910
- const report = { from: location.pathname, at: Date.now(), data: null };
911
- if (!element || !end || shareOnScreen(end.box) < (options.inView ?? IN_VIEW)) {
912
- if (from && new URL(from.u).pathname === location.pathname) {
913
- store?.p({ ...report, none: true });
914
- }
915
- return;
916
- }
917
- if (store && !store.o()) store.p(report);
918
- const layer = stage.layer(element, end, null);
919
- writeSession(MORPH_KEY, {
920
- n: way.name,
921
- m: way.name,
922
- h: markup(layer),
923
- e: end,
924
- t: false,
925
- ...backFields(from, stage.name)
926
- });
927
- };
928
- addEventListener("pagehide", said);
929
- stage.onRemove.push(() => removeEventListener("pagehide", said));
930
- };
931
- }
932
-
933
- // src/morph/finders.ts
934
- var SEEN_KEY = `${MORPH_KEY}:seen:`;
935
- function here() {
936
- return (_to, selector, measure) => {
937
- const element = onPage(selector);
938
- return element && shifted(measure(element), scrollX, scrollY);
939
- };
940
- }
941
- var seenKey = (selector) => `${SEEN_KEY}${selector}:${innerWidth}x${innerHeight}`;
942
- var SEEN_PAGES = 20;
943
- function seen() {
944
- const finder = (to, selector) => {
945
- const pages = readSession(seenKey(selector)) ?? {};
946
- const others = Object.keys(pages).filter(
947
- (path) => path !== location.pathname
948
- );
949
- return pages[to.pathname] ?? pages[others[others.length - 1] ?? ""] ?? null;
950
- };
951
- finder.start = ({ morph, atRest }) => {
952
- const say = () => {
953
- const element = onPage(morph.to);
954
- if (!element) return;
955
- const end = atRest(element);
956
- if (end.box.width > 0) {
957
- const key = seenKey(morph.to);
958
- const { [location.pathname]: _, ...pages } = readSession(key) ?? {};
959
- const kept = Object.entries(pages).slice(1 - SEEN_PAGES);
960
- writeSession(key, {
961
- ...Object.fromEntries(kept),
962
- [location.pathname]: shifted(end, scrollX, scrollY)
963
- });
964
- }
965
- };
966
- if (document.readyState === "complete") say();
967
- else addEventListener("load", say, { once: true });
968
- };
969
- return finder;
970
- }
971
-
972
- export {
973
- within,
974
- isEnding,
975
- ownCorners,
976
- cornersCss,
977
- endOf,
978
- atTheirEnd,
979
- LAYER_STYLE,
980
- CONTENT_STYLE,
981
- cloned,
982
- sizeOf,
983
- centredOn,
984
- markup,
985
- MORPH_KEY,
986
- onPage,
987
- over,
988
- isDefined,
989
- defineMorph,
990
- forgetWayOut,
991
- IN_VIEW,
992
- shareOnScreen,
993
- backFields,
994
- putBackFields,
995
- wayBackOf,
996
- wayOut,
997
- back,
998
- here,
999
- seen
1000
- };