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
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CONTENT_STYLE,
|
|
3
|
+
IN_VIEW,
|
|
4
|
+
LAYER_STYLE,
|
|
5
|
+
MORPH_KEY,
|
|
6
|
+
atTheirEnd,
|
|
7
|
+
back,
|
|
8
|
+
backFields,
|
|
9
|
+
centredOn,
|
|
10
|
+
cloned,
|
|
11
|
+
cornersCss,
|
|
12
|
+
defineMorph,
|
|
13
|
+
endOf,
|
|
14
|
+
forgetWayOut,
|
|
15
|
+
here,
|
|
16
|
+
isEnding,
|
|
17
|
+
markup,
|
|
18
|
+
onPage,
|
|
19
|
+
over,
|
|
20
|
+
ownCorners,
|
|
21
|
+
putBackFields,
|
|
22
|
+
seen,
|
|
23
|
+
shareOnScreen,
|
|
24
|
+
sizeOf,
|
|
25
|
+
wayBackOf,
|
|
26
|
+
wayOut,
|
|
27
|
+
within
|
|
28
|
+
} from "./chunks/chunk-3SU4CIKL.js";
|
|
29
|
+
import {
|
|
30
|
+
defineMove
|
|
31
|
+
} from "./chunks/chunk-U3XN54YY.js";
|
|
32
|
+
import "./chunks/chunk-3JAWIMHB.js";
|
|
33
|
+
import {
|
|
34
|
+
isFocusGivenBack,
|
|
35
|
+
reducesMotion
|
|
36
|
+
} from "./chunks/chunk-AEOAXMU3.js";
|
|
37
|
+
import {
|
|
38
|
+
isDeclined
|
|
39
|
+
} from "./chunks/chunk-YAL4MNCU.js";
|
|
40
|
+
|
|
41
|
+
// src/morph/frame.ts
|
|
42
|
+
var FRAME_STYLE = "position:fixed;top:0;border:0;margin:0;padding:0;max-width:none;max-height:none;pointer-events:none";
|
|
43
|
+
function loadInFrame(address, style, name = "") {
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
let frame = document.createElement("iframe");
|
|
46
|
+
frame.name = name, frame.setAttribute("aria-hidden", "true"), frame.tabIndex = -1, frame.inert = !0, frame.style.cssText = `${FRAME_STYLE};width:${document.documentElement.clientWidth}px;height:${innerHeight}px;${style}`;
|
|
47
|
+
let fail = () => {
|
|
48
|
+
frame.remove(), resolve(null);
|
|
49
|
+
};
|
|
50
|
+
frame.addEventListener("error", fail), frame.addEventListener("load", async () => {
|
|
51
|
+
try {
|
|
52
|
+
if (await frame.contentDocument?.fonts.ready, !frame.contentDocument?.body) throw new Error("no page");
|
|
53
|
+
resolve(frame);
|
|
54
|
+
} catch {
|
|
55
|
+
fail();
|
|
56
|
+
}
|
|
57
|
+
}), frame.src = address, document.documentElement.append(frame);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
var INTENTS = ["pointerover", "focusin", "touchstart"];
|
|
61
|
+
function onApproach(tell) {
|
|
62
|
+
let last = null, heard = (event) => {
|
|
63
|
+
let link = event.target instanceof Element ? event.target.closest("a[href]") : null;
|
|
64
|
+
link !== last && (last = link, link && !isFocusGivenBack(link) && !reducesMotion() && tell({
|
|
65
|
+
link,
|
|
66
|
+
via: "link",
|
|
67
|
+
from: new URL(location.href),
|
|
68
|
+
to: new URL(link.href),
|
|
69
|
+
canHandOver: !0
|
|
70
|
+
}));
|
|
71
|
+
};
|
|
72
|
+
for (let intent of INTENTS)
|
|
73
|
+
addEventListener(intent, heard, { passive: !0, capture: !0 });
|
|
74
|
+
return () => {
|
|
75
|
+
for (let intent of INTENTS)
|
|
76
|
+
removeEventListener(intent, heard, { capture: !0 });
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/morph/behind.ts
|
|
81
|
+
var BEHIND_WAIT_MS = 800;
|
|
82
|
+
function behind(options = {}) {
|
|
83
|
+
return (stage) => {
|
|
84
|
+
let way = wayBackOf(stage);
|
|
85
|
+
stage.appear = options.appear;
|
|
86
|
+
let loading = null, dropFrame = () => {
|
|
87
|
+
loading?.frame.then((frame) => frame?.remove()), loading = null;
|
|
88
|
+
};
|
|
89
|
+
function showBehind(address, from) {
|
|
90
|
+
if (loading?.address !== address) {
|
|
91
|
+
dropFrame();
|
|
92
|
+
let frame = loadInFrame(
|
|
93
|
+
address,
|
|
94
|
+
// Just under the layer, above everything of the page's.
|
|
95
|
+
"left:0;opacity:0;z-index:2147483646",
|
|
96
|
+
`${MORPH_KEY}:${JSON.stringify({ x: from.x, y: from.y, m: way.name, d: from.d, p: from.p, ...putBackFields(from, stage.name) })}`
|
|
97
|
+
).then((loaded) => {
|
|
98
|
+
let view = loaded?.contentWindow;
|
|
99
|
+
if (!loaded || !view) return null;
|
|
100
|
+
view.scrollTo(from.x, from.y);
|
|
101
|
+
let there = loaded.contentDocument?.querySelector(
|
|
102
|
+
from.p
|
|
103
|
+
);
|
|
104
|
+
return there && (there.style.visibility = "hidden"), new Promise(
|
|
105
|
+
(resolve) => view.requestAnimationFrame(
|
|
106
|
+
() => view.requestAnimationFrame(() => resolve(loaded))
|
|
107
|
+
)
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
loading = { address, frame };
|
|
111
|
+
}
|
|
112
|
+
return loading.frame;
|
|
113
|
+
}
|
|
114
|
+
way.aims = (from, end, about) => {
|
|
115
|
+
let ends = {
|
|
116
|
+
from: { ...from.e },
|
|
117
|
+
to: end
|
|
118
|
+
};
|
|
119
|
+
return isDeclined(stage.morph.ends?.(ends, about)) ? !1 : { stop: ends.from, end: ends.to ?? end };
|
|
120
|
+
}, way.frameBehind = (visit, from) => within(showBehind(visit.to.href, from), options.wait ?? BEHIND_WAIT_MS), way.dropFrame = dropFrame, stage.onRemove.push(
|
|
121
|
+
dropFrame,
|
|
122
|
+
onApproach((visit) => {
|
|
123
|
+
let from = way.origin();
|
|
124
|
+
from && way.leadsBack?.(visit) && showBehind(visit.to.href, from);
|
|
125
|
+
})
|
|
126
|
+
);
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/morph/copy.ts
|
|
131
|
+
function copy() {
|
|
132
|
+
return {
|
|
133
|
+
build(element) {
|
|
134
|
+
let layer = document.createElement("div"), content = cloned(element), own = element, drawn = own.offsetWidth && own.offsetHeight ? null : element.getBoundingClientRect(), width = own.offsetWidth || (drawn?.width ?? 0), height = own.offsetHeight || (drawn?.height ?? 0);
|
|
135
|
+
return content.style.cssText += `;width:${width}px;height:${height}px;${CONTENT_STYLE}`, content.dataset.turnContent = "", layer.style.cssText = `${LAYER_STYLE};width:${width}px;height:${height}px`, layer.append(content), layer;
|
|
136
|
+
},
|
|
137
|
+
pose(layer) {
|
|
138
|
+
let [width, height] = sizeOf(layer);
|
|
139
|
+
return ({ box, rotate = 0, radius }) => {
|
|
140
|
+
let x = box.width / width, y = box.height / height;
|
|
141
|
+
return {
|
|
142
|
+
move: {
|
|
143
|
+
translate: centredOn(box, width, height),
|
|
144
|
+
rotate: `${rotate}deg`,
|
|
145
|
+
scale: `${x} ${y}`
|
|
146
|
+
},
|
|
147
|
+
look: {
|
|
148
|
+
borderRadius: `${cornersCss(radius, x)} / ${cornersCss(radius, y)}`
|
|
149
|
+
},
|
|
150
|
+
on: layer.firstElementChild
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/morph/link-back.ts
|
|
158
|
+
function linkBack(options = {}) {
|
|
159
|
+
return (stage) => {
|
|
160
|
+
let way = wayBackOf(stage);
|
|
161
|
+
way.byLink = options, Object.assign(way.keep, options.keep);
|
|
162
|
+
let isMarked = (link) => link.getAttribute("data-turn-back") === stage.name;
|
|
163
|
+
way.leadsBack = (visit) => isMarked(visit.link) ? !0 : options.match ? options.match(visit) : visit.to.pathname === way.origin()?.f;
|
|
164
|
+
let started = way.origin();
|
|
165
|
+
for (let link of document.querySelectorAll(
|
|
166
|
+
"a[data-turn-back]"
|
|
167
|
+
))
|
|
168
|
+
if (isMarked(link) && (link.dataset.turn = way.name, started && started.f !== link.pathname)) {
|
|
169
|
+
link.href = started.f;
|
|
170
|
+
let label = link.dataset.turnBackLabel;
|
|
171
|
+
label && link.setAttribute("aria-label", label);
|
|
172
|
+
}
|
|
173
|
+
stage.onRemove.push(
|
|
174
|
+
defineMove(way.name, {
|
|
175
|
+
match: way.leadsBack,
|
|
176
|
+
async leave(turn) {
|
|
177
|
+
let origin = way.origin(), from = origin?.f === turn.to.pathname ? origin : null, element = onPage(stage.morph.to);
|
|
178
|
+
if (!element || !turn.canHandOver || stage.live)
|
|
179
|
+
return turn.decline();
|
|
180
|
+
let about = stage.about(null, !0), end = stage.read(element), stop = null;
|
|
181
|
+
if (from && way.aims) {
|
|
182
|
+
let aimed = way.aims(from, end, about);
|
|
183
|
+
if (aimed === !1) return turn.decline();
|
|
184
|
+
({ stop, end } = aimed);
|
|
185
|
+
}
|
|
186
|
+
if (shareOnScreen(end.box) < (options.inView ?? IN_VIEW))
|
|
187
|
+
return turn.decline();
|
|
188
|
+
let frame = from && stop ? await way.frameBehind?.(turn, from) ?? null : null, layer = await stage.put(element, end, stop), leaving = turn.leaving();
|
|
189
|
+
stop && await stage.travel(
|
|
190
|
+
end,
|
|
191
|
+
stop,
|
|
192
|
+
about,
|
|
193
|
+
stage.timing(options),
|
|
194
|
+
!0,
|
|
195
|
+
frame
|
|
196
|
+
), await leaving;
|
|
197
|
+
let scrollsBack = from && (stop || options.scroll !== !1 && !turn.to.hash);
|
|
198
|
+
return stage.hand({
|
|
199
|
+
n: way.name,
|
|
200
|
+
m: way.name,
|
|
201
|
+
h: markup(layer),
|
|
202
|
+
e: stop ?? end,
|
|
203
|
+
...stop ? { f: end } : {},
|
|
204
|
+
t: !!stop,
|
|
205
|
+
b: !!frame,
|
|
206
|
+
...backFields(from, stage.name),
|
|
207
|
+
...from && scrollsBack ? { x: from.x, y: from.y } : {}
|
|
208
|
+
}), { data: from?.d ?? null };
|
|
209
|
+
},
|
|
210
|
+
stay(turn, stay) {
|
|
211
|
+
stage.drop(), way.dropFrame?.(), stage.morph.stay?.(turn, stay);
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
);
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/morph/peek.ts
|
|
219
|
+
var PEEK_WAIT_MS = 1500;
|
|
220
|
+
function peek(options = {}) {
|
|
221
|
+
let asked = /* @__PURE__ */ new Map(), measureThere = (to, selector, measure) => {
|
|
222
|
+
let key = `${to.href} ${selector} ${innerWidth}x${innerHeight}`, answer = asked.get(key);
|
|
223
|
+
return answer || (answer = loadInFrame(to.href, "left:-200vw;visibility:hidden").then(
|
|
224
|
+
(frame) => {
|
|
225
|
+
let page = frame?.contentDocument, element = page?.querySelector(selector);
|
|
226
|
+
for (let animation of page?.getAnimations() ?? [])
|
|
227
|
+
isEnding(animation) && animation.finish();
|
|
228
|
+
let end = element ? measure(element) : null;
|
|
229
|
+
return frame?.remove(), end || asked.delete(key), end;
|
|
230
|
+
}
|
|
231
|
+
), asked.set(key, answer)), answer;
|
|
232
|
+
}, finder = (to, selector, measure) => within(measureThere(to, selector, measure), options.wait ?? PEEK_WAIT_MS);
|
|
233
|
+
return (options.warm ?? !0) && (finder.start = (stage) => {
|
|
234
|
+
stage.onRemove.push(
|
|
235
|
+
onApproach((visit) => {
|
|
236
|
+
let { match, from } = stage.morph;
|
|
237
|
+
(match ? match(visit) : from?.(visit.link)) && measureThere(visit.to, stage.morph.to, stage.read);
|
|
238
|
+
})
|
|
239
|
+
);
|
|
240
|
+
}), finder;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/morph/shadow.ts
|
|
244
|
+
function asDropShadow(shadow2) {
|
|
245
|
+
if (/^[-\d.]/.test(shadow2)) return shadow2;
|
|
246
|
+
let [, color, x, y, blur] = /^(.*?\)|\S+) (\S+) (\S+) (\S+)/.exec(shadow2) ?? [];
|
|
247
|
+
return color && blur ? `${x} ${y} ${blur} ${color}` : void 0;
|
|
248
|
+
}
|
|
249
|
+
var dropShadow = (shadow2) => `drop-shadow(${shadow2})`;
|
|
250
|
+
function shadow() {
|
|
251
|
+
return (stage) => {
|
|
252
|
+
let { read } = stage;
|
|
253
|
+
stage.read = (element) => {
|
|
254
|
+
let end = read(element), own = asDropShadow(getComputedStyle(element).boxShadow);
|
|
255
|
+
return own && (end.shadow = own), end;
|
|
256
|
+
}, stage.onPlay.push((layer, first, last, timing, finished) => {
|
|
257
|
+
let start = first.shadow && asDropShadow(first.shadow), stop = last.shadow && asDropShadow(last.shadow), one = start ?? stop;
|
|
258
|
+
if (!one) return;
|
|
259
|
+
let caster = document.createElement("div");
|
|
260
|
+
caster.setAttribute("aria-hidden", "true"), caster.dataset.turnShadow = "", caster.style.cssText = `position:fixed;left:0;top:0;z-index:2147483646;pointer-events:none;filter:${dropShadow(one)}`;
|
|
261
|
+
let shape = document.createElement("div");
|
|
262
|
+
shape.style.cssText = `background:#000;width:${layer.style.width};height:${layer.style.height}`;
|
|
263
|
+
for (let animation of layer.getAnimations()) {
|
|
264
|
+
let { effect } = animation;
|
|
265
|
+
effect instanceof KeyframeEffect && shape.animate(effect.getKeyframes(), timing);
|
|
266
|
+
}
|
|
267
|
+
caster.animate(
|
|
268
|
+
start && stop ? { filter: [dropShadow(start), dropShadow(stop)] } : { opacity: start ? [1, 0] : [0, 1] },
|
|
269
|
+
timing
|
|
270
|
+
), caster.append(shape), layer.before(caster), finished.then(() => caster.remove());
|
|
271
|
+
});
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// src/morph/text.ts
|
|
276
|
+
var TYPE = [
|
|
277
|
+
"fontSize",
|
|
278
|
+
"fontWeight",
|
|
279
|
+
"fontStretch",
|
|
280
|
+
"letterSpacing",
|
|
281
|
+
"fontVariationSettings"
|
|
282
|
+
], FACE = [
|
|
283
|
+
"fontFamily",
|
|
284
|
+
"fontStyle",
|
|
285
|
+
"textTransform",
|
|
286
|
+
"fontKerning",
|
|
287
|
+
"fontFeatureSettings"
|
|
288
|
+
];
|
|
289
|
+
function glyphs(element) {
|
|
290
|
+
let range = element.ownerDocument.createRange();
|
|
291
|
+
range.selectNodeContents(element);
|
|
292
|
+
let { left, top, width, height } = range.getBoundingClientRect();
|
|
293
|
+
return { left, top, width, height };
|
|
294
|
+
}
|
|
295
|
+
function splitDifference(one, other) {
|
|
296
|
+
let least = Math.min(one.length, other.length), head = 0;
|
|
297
|
+
for (; head < least && one[head] === other[head]; ) head++;
|
|
298
|
+
let tail = 0;
|
|
299
|
+
for (; tail < least - head && one[one.length - 1 - tail] === other[other.length - 1 - tail]; )
|
|
300
|
+
tail++;
|
|
301
|
+
return [
|
|
302
|
+
one.slice(0, head),
|
|
303
|
+
one.slice(head, one.length - tail),
|
|
304
|
+
other.slice(head, other.length - tail),
|
|
305
|
+
other.slice(other.length - tail)
|
|
306
|
+
];
|
|
307
|
+
}
|
|
308
|
+
function text(options = {}) {
|
|
309
|
+
let pseudoOf = (isTo) => typeof options.box == "object" ? options.box[isTo ? "to" : "from"] : options.box, part = (layer, name) => layer.querySelector(`[data-turn-part="${name}"]`);
|
|
310
|
+
return {
|
|
311
|
+
read(element, end, isTo) {
|
|
312
|
+
let style = getComputedStyle(element), type = {};
|
|
313
|
+
for (let property of TYPE) type[property] = style[property];
|
|
314
|
+
type.letterSpacing === "normal" && (type.letterSpacing = "0px"), type.fontVariationSettings === "normal" && (type.fontVariationSettings = `"opsz" ${Number.parseFloat(style.fontSize)}`);
|
|
315
|
+
let box = glyphs(element), words = (element.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
316
|
+
if (end.background = style.backgroundColor, element.getClientRects().length === 0) {
|
|
317
|
+
let copy2 = document.createElement("span");
|
|
318
|
+
copy2.style.cssText = "position:fixed;left:0;top:0;white-space:nowrap;line-height:1;font-optical-sizing:none;visibility:hidden";
|
|
319
|
+
for (let property of FACE) copy2.style[property] = style[property];
|
|
320
|
+
Object.assign(copy2.style, type), copy2.textContent = words, document.documentElement.append(copy2), box = glyphs(copy2), copy2.remove(), end.box = { ...box };
|
|
321
|
+
} else if (pseudoOf(isTo)) {
|
|
322
|
+
let drawn = getComputedStyle(element, pseudoOf(isTo)), [left, top, width, height] = [
|
|
323
|
+
drawn.left,
|
|
324
|
+
drawn.top,
|
|
325
|
+
drawn.width,
|
|
326
|
+
drawn.height
|
|
327
|
+
].map(Number.parseFloat);
|
|
328
|
+
if (drawn.content !== "none" && [left, top, width, height].every(Number.isFinite)) {
|
|
329
|
+
let own = element.getBoundingClientRect();
|
|
330
|
+
end.box = {
|
|
331
|
+
left: own.left + element.clientLeft + (left ?? 0),
|
|
332
|
+
top: own.top + element.clientTop + (top ?? 0),
|
|
333
|
+
width: width ?? 0,
|
|
334
|
+
height: height ?? 0
|
|
335
|
+
}, end.radius = ownCorners(drawn, width ?? 0), end.background = drawn.backgroundColor;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
end.text = {
|
|
339
|
+
box,
|
|
340
|
+
type,
|
|
341
|
+
color: style.color,
|
|
342
|
+
words
|
|
343
|
+
};
|
|
344
|
+
},
|
|
345
|
+
build(element, from) {
|
|
346
|
+
let layer = document.createElement("div"), content = document.createElement("span"), fill = document.createElement("span"), style = getComputedStyle(element), type = from.text?.type ?? {};
|
|
347
|
+
layer.style.cssText = `${LAYER_STYLE};width:0;height:0;white-space:nowrap;line-height:1;font-optical-sizing:none`;
|
|
348
|
+
for (let property of FACE) layer.style[property] = style[property];
|
|
349
|
+
return Object.assign(layer.style, type), content.style.cssText = "position:absolute;left:0;top:0", content.textContent = from.text?.words ?? "", content.dataset.turnContent = "", content.dataset.turnWords = "", fill.style.cssText = "position:absolute;z-index:-1", fill.dataset.turnBox = "", layer.append(content, fill), layer;
|
|
350
|
+
},
|
|
351
|
+
pose(layer) {
|
|
352
|
+
let fill = layer.lastElementChild, insetOf = (words) => {
|
|
353
|
+
let probe = layer.cloneNode(!1), line = document.createElement("span");
|
|
354
|
+
probe.removeAttribute("popover"), delete probe.dataset.turnLayer, Object.assign(probe.style, words.type, {
|
|
355
|
+
translate: "none",
|
|
356
|
+
rotate: "none",
|
|
357
|
+
visibility: "hidden"
|
|
358
|
+
}), line.style.cssText = "position:absolute;left:0;top:0", line.textContent = words.words, probe.append(line), document.documentElement.append(probe);
|
|
359
|
+
let corner = probe.getBoundingClientRect(), drawn = glyphs(line);
|
|
360
|
+
return probe.remove(), [drawn.left - corner.left, drawn.top - corner.top];
|
|
361
|
+
};
|
|
362
|
+
return ({ text: words, rotate = 0, box, radius, background }) => {
|
|
363
|
+
if (!words) throw new Error("pageturn/morph: an end has no text");
|
|
364
|
+
let [x, y] = insetOf(words), left = words.box.left - x, top = words.box.top - y;
|
|
365
|
+
return {
|
|
366
|
+
// By a translation, not `left` and `top`: Safari does not show those travel.
|
|
367
|
+
move: { translate: `${left}px ${top}px`, rotate: `${rotate}deg` },
|
|
368
|
+
look: {
|
|
369
|
+
...words.type,
|
|
370
|
+
color: words.color,
|
|
371
|
+
// Around the middle of its box: a site that scales the layer (a button that
|
|
372
|
+
// gives under the press) scales it around what is seen.
|
|
373
|
+
transformOrigin: `${box.left - left + box.width / 2}px ${box.top - top + box.height / 2}px`
|
|
374
|
+
},
|
|
375
|
+
on: layer,
|
|
376
|
+
// The element's own box behind its words: a button's pill, a title's band. In
|
|
377
|
+
// pixels: a length in em inside a keyframe is not read at the animated font
|
|
378
|
+
// size by every browser.
|
|
379
|
+
inner: {
|
|
380
|
+
on: fill,
|
|
381
|
+
look: {
|
|
382
|
+
left: `${box.left - left}px`,
|
|
383
|
+
top: `${box.top - top}px`,
|
|
384
|
+
width: `${box.width}px`,
|
|
385
|
+
height: `${box.height}px`,
|
|
386
|
+
borderRadius: cornersCss(radius),
|
|
387
|
+
backgroundColor: background ?? "transparent"
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
};
|
|
392
|
+
},
|
|
393
|
+
play(layer, start, stop, timing, back2) {
|
|
394
|
+
let content = layer.firstElementChild, [from, to] = [start.text?.words ?? "", stop.text?.words ?? ""];
|
|
395
|
+
if (from === to || !to || content.firstElementChild) return;
|
|
396
|
+
let [head, leaving, arriving, rest] = splitDifference(from, to), swap = (typeof options.swap == "function" ? options.swap({ back: back2 }) : options.swap) ?? {}, span = (name, words, style) => `<span data-turn-part="${name}" style="${style}">${words.replace(/[&<]/g, (sign) => sign === "&" ? "&" : "<")}</span>`;
|
|
397
|
+
content.innerHTML = span("head", head, "") + `<span style="position:relative;display:inline-block">${span("arriving", arriving, "display:inline-block")}${span("leaving", leaving, "position:absolute;left:0;top:0")}</span><span data-turn-part="fit" style="display:inline-block">${span("rest", rest, "display:inline-block;white-space:pre")}</span>`;
|
|
398
|
+
let [fit, restPart, leavingPart, arrivingPart] = [
|
|
399
|
+
"fit",
|
|
400
|
+
"rest",
|
|
401
|
+
"leaving",
|
|
402
|
+
"arriving"
|
|
403
|
+
].map((name) => part(layer, name)), slot = arrivingPart?.parentElement;
|
|
404
|
+
swap.clip && (slot.style.clipPath = swap.clip);
|
|
405
|
+
let widthOf = (element) => element?.getBoundingClientRect().width ?? 0, shareToClose = () => 100 * (widthOf(arrivingPart) - widthOf(leavingPart)) / (widthOf(restPart) || 1), atStart = shareToClose(), atStop = atTheirEnd(layer.getAnimations(), shareToClose), swapTiming = over(timing, swap);
|
|
406
|
+
fit?.animate({ translate: [`${-atStart}% 0`, `${-atStop}% 0`] }, timing), restPart?.animate({ translate: ["0% 0", `${atStop}% 0`] }, swapTiming), leavingPart?.animate(
|
|
407
|
+
swap.leave ?? [{ opacity: 1 }, { opacity: 0 }],
|
|
408
|
+
swapTiming
|
|
409
|
+
), arrivingPart?.animate(
|
|
410
|
+
swap.arrive ?? [{ opacity: 0 }, { opacity: 1 }],
|
|
411
|
+
swapTiming
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// src/morph/turned.ts
|
|
418
|
+
function ownTransform(style) {
|
|
419
|
+
let [x = 1, y = x] = style.scale.split(" ").map(Number.parseFloat);
|
|
420
|
+
return new DOMMatrix().rotate(Number.parseFloat(style.rotate) || 0).scale(x || 1, y || 1).multiply(new DOMMatrix(style.transform));
|
|
421
|
+
}
|
|
422
|
+
function turned() {
|
|
423
|
+
return (stage) => {
|
|
424
|
+
stage.endOf = (element) => {
|
|
425
|
+
let end = endOf(element), matrix = new DOMMatrix();
|
|
426
|
+
for (let node = element; node; node = node.parentElement)
|
|
427
|
+
matrix = ownTransform(getComputedStyle(node)).multiply(matrix);
|
|
428
|
+
let rotate = Math.atan2(matrix.b, matrix.a) * 180 / Math.PI, scaleX = Math.hypot(matrix.a, matrix.b), own = element, isTurned = Math.abs(rotate) > 0.01 && own.offsetWidth > 0, { box } = end, width = isTurned ? own.offsetWidth * scaleX : box.width, height = isTurned ? own.offsetHeight * Math.hypot(matrix.c, matrix.d) : box.height;
|
|
429
|
+
return {
|
|
430
|
+
...end,
|
|
431
|
+
box: {
|
|
432
|
+
left: box.left + (box.width - width) / 2,
|
|
433
|
+
top: box.top + (box.height - height) / 2,
|
|
434
|
+
width,
|
|
435
|
+
height
|
|
436
|
+
},
|
|
437
|
+
rotate,
|
|
438
|
+
radius: ownCorners(getComputedStyle(element), width, scaleX)
|
|
439
|
+
};
|
|
440
|
+
};
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
export {
|
|
444
|
+
back,
|
|
445
|
+
behind,
|
|
446
|
+
copy,
|
|
447
|
+
defineMorph,
|
|
448
|
+
forgetWayOut,
|
|
449
|
+
here,
|
|
450
|
+
linkBack,
|
|
451
|
+
peek,
|
|
452
|
+
seen,
|
|
453
|
+
shadow,
|
|
454
|
+
text,
|
|
455
|
+
turned,
|
|
456
|
+
wayOut
|
|
457
|
+
};
|
package/dist/dev/next.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import "./chunks/chunk-U3XN54YY.js";
|
|
3
|
+
import {
|
|
4
|
+
startNavigation
|
|
5
|
+
} from "./chunks/chunk-I4SBUABE.js";
|
|
6
|
+
import "./chunks/chunk-3JAWIMHB.js";
|
|
7
|
+
import {
|
|
8
|
+
inBrowser
|
|
9
|
+
} from "./chunks/chunk-AEOAXMU3.js";
|
|
10
|
+
import "./chunks/chunk-YAL4MNCU.js";
|
|
11
|
+
|
|
12
|
+
// src/next/next.ts
|
|
13
|
+
import NextLink from "next/link.js";
|
|
14
|
+
import { usePathname, useRouter, useSearchParams } from "next/navigation.js";
|
|
15
|
+
import {
|
|
16
|
+
createElement,
|
|
17
|
+
Suspense,
|
|
18
|
+
useLayoutEffect,
|
|
19
|
+
useMemo,
|
|
20
|
+
useRef
|
|
21
|
+
} from "react";
|
|
22
|
+
var pending = null, shownAt = null;
|
|
23
|
+
function go(router, href, how, link, options) {
|
|
24
|
+
let turn = startNavigation({
|
|
25
|
+
to: link ? link.href : new URL(href, location.href),
|
|
26
|
+
via: link ? "link" : "script",
|
|
27
|
+
link
|
|
28
|
+
});
|
|
29
|
+
pending = turn, router.prefetch(href), turn.left.then(() => {
|
|
30
|
+
pending === turn && router[how](href, options);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function Link(props) {
|
|
34
|
+
let router = useRouter(), anchor = useRef(null), { href, replace, scroll, onNavigate, ref } = props;
|
|
35
|
+
return createElement(NextLink, {
|
|
36
|
+
...props,
|
|
37
|
+
ref(element) {
|
|
38
|
+
anchor.current = element, typeof ref == "function" ? ref(element) : ref && (ref.current = element);
|
|
39
|
+
},
|
|
40
|
+
onNavigate(event) {
|
|
41
|
+
let isStopped = !1;
|
|
42
|
+
onNavigate?.({
|
|
43
|
+
preventDefault() {
|
|
44
|
+
isStopped = !0, event.preventDefault();
|
|
45
|
+
}
|
|
46
|
+
}), !(isStopped || typeof href != "string" || !inBrowser) && (event.preventDefault(), go(router, href, replace ? "replace" : "push", anchor.current, {
|
|
47
|
+
scroll: scroll !== !1
|
|
48
|
+
}));
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function usePageturnRouter() {
|
|
53
|
+
let router = useRouter();
|
|
54
|
+
return useMemo(
|
|
55
|
+
() => ({
|
|
56
|
+
...router,
|
|
57
|
+
push: (href, options) => go(router, href, "push", null, options),
|
|
58
|
+
replace: (href, options) => go(router, href, "replace", null, options)
|
|
59
|
+
}),
|
|
60
|
+
[router]
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
function Watcher() {
|
|
64
|
+
let pathname = usePathname(), search = useSearchParams().toString();
|
|
65
|
+
return useLayoutEffect(() => {
|
|
66
|
+
let here = location.href, isFirst = shownAt === null;
|
|
67
|
+
if (shownAt = here, isFirst) return;
|
|
68
|
+
let turn = pending ?? startNavigation({ to: here, via: "history" });
|
|
69
|
+
pending = null, turn.swapped();
|
|
70
|
+
}, [pathname, search]), null;
|
|
71
|
+
}
|
|
72
|
+
function Pageturn() {
|
|
73
|
+
return createElement(Suspense, { fallback: null }, createElement(Watcher));
|
|
74
|
+
}
|
|
75
|
+
export {
|
|
76
|
+
Link,
|
|
77
|
+
Pageturn,
|
|
78
|
+
usePageturnRouter
|
|
79
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import {
|
|
2
|
+
pageturn
|
|
3
|
+
} from "./chunks/chunk-Y6TD2T53.js";
|
|
4
|
+
import "./chunks/chunk-U3XN54YY.js";
|
|
5
|
+
import "./chunks/chunk-I4SBUABE.js";
|
|
6
|
+
import "./chunks/chunk-3JAWIMHB.js";
|
|
7
|
+
import "./chunks/chunk-AEOAXMU3.js";
|
|
8
|
+
import "./chunks/chunk-YAL4MNCU.js";
|
|
9
|
+
|
|
10
|
+
// src/nuxt/plugin.ts
|
|
11
|
+
var SHOWN_MAX_MS = 4e3;
|
|
12
|
+
function pageturnPlugin(nuxtApp) {
|
|
13
|
+
pageturn(nuxtApp.$router, {
|
|
14
|
+
whenShown(swap) {
|
|
15
|
+
let isDone = !1, once = () => {
|
|
16
|
+
isDone || (isDone = !0, swap());
|
|
17
|
+
};
|
|
18
|
+
nuxtApp.hooks.hookOnce("page:finish", once), setTimeout(once, SHOWN_MAX_MS);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
pageturnPlugin as default
|
|
24
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import "./chunks/chunk-U3XN54YY.js";
|
|
2
|
+
import {
|
|
3
|
+
startNavigation
|
|
4
|
+
} from "./chunks/chunk-I4SBUABE.js";
|
|
5
|
+
import "./chunks/chunk-3JAWIMHB.js";
|
|
6
|
+
import {
|
|
7
|
+
inBrowser
|
|
8
|
+
} from "./chunks/chunk-AEOAXMU3.js";
|
|
9
|
+
import "./chunks/chunk-YAL4MNCU.js";
|
|
10
|
+
|
|
11
|
+
// src/react-router/react-router.ts
|
|
12
|
+
import { useLayoutEffect } from "react";
|
|
13
|
+
import { useLocation } from "react-router";
|
|
14
|
+
var current = null, popped = !1, isMounted = !1;
|
|
15
|
+
inBrowser && addEventListener(
|
|
16
|
+
"popstate",
|
|
17
|
+
() => {
|
|
18
|
+
popped = !0;
|
|
19
|
+
},
|
|
20
|
+
!0
|
|
21
|
+
);
|
|
22
|
+
var pageturnMiddleware = async ({ request }, next) => {
|
|
23
|
+
let via = popped ? "history" : "link";
|
|
24
|
+
if (popped = !1, !isMounted) return next();
|
|
25
|
+
let turn = startNavigation({ to: request.url, via });
|
|
26
|
+
current = turn, request.signal.addEventListener("abort", () => {
|
|
27
|
+
setTimeout(() => {
|
|
28
|
+
current === turn && (current = null, turn.cancelled());
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
let [result] = await Promise.all([next(), turn.left]);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
function usePageturn() {
|
|
35
|
+
let { key } = useLocation();
|
|
36
|
+
useLayoutEffect(() => {
|
|
37
|
+
isMounted = !0;
|
|
38
|
+
}, []), useLayoutEffect(() => {
|
|
39
|
+
let turn = current;
|
|
40
|
+
current = null, turn?.swapped();
|
|
41
|
+
}, [key]);
|
|
42
|
+
}
|
|
43
|
+
export {
|
|
44
|
+
pageturnMiddleware,
|
|
45
|
+
usePageturn
|
|
46
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import "./chunks/chunk-U3XN54YY.js";
|
|
2
|
+
import {
|
|
3
|
+
startNavigation
|
|
4
|
+
} from "./chunks/chunk-I4SBUABE.js";
|
|
5
|
+
import "./chunks/chunk-3JAWIMHB.js";
|
|
6
|
+
import "./chunks/chunk-AEOAXMU3.js";
|
|
7
|
+
import "./chunks/chunk-YAL4MNCU.js";
|
|
8
|
+
|
|
9
|
+
// src/sveltekit/sveltekit.ts
|
|
10
|
+
var started = null;
|
|
11
|
+
function begin(navigation) {
|
|
12
|
+
if (!navigation.to || navigation.willUnload) return null;
|
|
13
|
+
let turn = startNavigation({
|
|
14
|
+
to: navigation.to.url,
|
|
15
|
+
via: navigation.type === "link" ? "link" : navigation.type === "popstate" ? "history" : "script"
|
|
16
|
+
});
|
|
17
|
+
return navigation.complete.catch(() => {
|
|
18
|
+
started === turn && (started = null), turn.cancelled();
|
|
19
|
+
}), turn;
|
|
20
|
+
}
|
|
21
|
+
function pageturn({ beforeNavigate, onNavigate }) {
|
|
22
|
+
beforeNavigate((navigation) => {
|
|
23
|
+
started = begin(navigation);
|
|
24
|
+
}), onNavigate(async (navigation) => {
|
|
25
|
+
let turn = started ?? begin(navigation);
|
|
26
|
+
if (started = null, !!turn)
|
|
27
|
+
return await turn.left, () => turn.swapped();
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
export {
|
|
31
|
+
pageturn
|
|
32
|
+
};
|