@xingwangzhe/stalux 1.56.0 → 1.58.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/package.json +4 -12
- package/src/components/stalux/archives/archivesList.astro +1 -1
- package/src/components/stalux/categories/categoriesCard.astro +1 -1
- package/src/components/stalux/posts/PostTaxonomy.astro +1 -1
- package/src/components/stalux/posts/postCard.astro +3 -3
- package/src/components/stalux/tags/tagHeader.astro +2 -2
- package/src/components/stalux/tags/tagsCloud.astro +2 -2
- package/src/index.ts +39 -13
- package/src/internal/html-output.ts +18 -0
- package/src/internal/modern-css.ts +1 -0
- package/src/internal/route-hrefs.ts +47 -0
- package/src/internal/social-card-data.ts +1 -1
- package/src/internal/style-names.generated.ts +87 -94
- package/src/internal/swup-options.ts +98 -0
- package/src/layouts/Stalux.astro +0 -1
- package/src/pages/404.astro +1 -1
- package/src/pages/api/posts.json.ts +1 -1
- package/src/pages/atom.xml.ts +2 -0
- package/src/pages/posts/[post].md.ts +2 -2
- package/src/pages/rss.xml.ts +2 -0
- package/src/scripts/analytics-runtime.ts +4 -9
- package/src/scripts/list-reveal.ts +3 -15
- package/src/scripts/module-motion.ts +2 -3
- package/src/scripts/navigation-scripts.ts +12 -7
- package/src/scripts/route-guards.ts +200 -0
- package/src/scripts/swup-internals.d.ts +15 -0
- package/src/styles/base.css +0 -8
- package/src/styles/generated-prose.css +1 -1
- package/src/styles/generated-waline.css +1 -1
- package/src/styles/generated.css +1 -1
- package/src/styles/integrations.css +14 -16
- package/src/styles/math.css +3 -1
- package/src/styles/motion.css +1 -22
- package/src/utils/accent-color.ts +52 -0
- package/src/utils/ai-discovery.ts +1 -1
- package/src/utils/feed.ts +1 -1
- package/src/utils/public-routes.ts +14 -4
- package/src/utils/seo-document.ts +3 -2
- package/src/scripts/route-motion.ts +0 -340
- package/src/scripts/route-out.ts +0 -63
- package/src/scripts/smooth-scroll.ts +0 -139
- package/src/scripts/swup-router.ts +0 -580
- package/src/scripts/view-transitions.ts +0 -4
- package/src/utils/route-motion-policy.ts +0 -72
package/src/scripts/route-out.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import type { RouteMotionPreset } from "../utils/route-motion-policy";
|
|
2
|
-
import { getRouteStage, getStageExit } from "./route-motion";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Fade and shift the current content stage out, continuing the entry direction
|
|
6
|
-
* so the two halves read as one movement instead of a flash.
|
|
7
|
-
*
|
|
8
|
-
* The returned release keeps the fill effect until the entry cue takes over.
|
|
9
|
-
* Cancelling here would expose the freshly swapped document at full opacity for
|
|
10
|
-
* a frame before it animates in — the exact flash this replaces.
|
|
11
|
-
*/
|
|
12
|
-
export async function animateRouteOut(
|
|
13
|
-
preset: RouteMotionPreset,
|
|
14
|
-
signal: AbortSignal,
|
|
15
|
-
): Promise<() => void> {
|
|
16
|
-
const spec = getStageExit(preset);
|
|
17
|
-
const stage = spec ? getRouteStage() : null;
|
|
18
|
-
if (
|
|
19
|
-
!(spec && stage) ||
|
|
20
|
-
signal.aborted ||
|
|
21
|
-
document.hidden ||
|
|
22
|
-
typeof stage.animate !== "function" ||
|
|
23
|
-
window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
24
|
-
)
|
|
25
|
-
return () => {};
|
|
26
|
-
const mobile = window.matchMedia("(max-width: 768px)").matches;
|
|
27
|
-
const distance = mobile ? Math.round(spec.distance * 0.6) : spec.distance;
|
|
28
|
-
const to =
|
|
29
|
-
spec.axis === "x" ? `translate3d(${distance}px, 0, 0)` : `translate3d(0, ${distance}px, 0)`;
|
|
30
|
-
// Article-to-article has no distance: the column fades out in place, matching
|
|
31
|
-
// the entry fade, so the shell stays put and only the content disappears.
|
|
32
|
-
const keyframes: PropertyIndexedKeyframes = { opacity: [1, 0] };
|
|
33
|
-
if (distance !== 0) keyframes.transform = ["translate3d(0, 0, 0)", to];
|
|
34
|
-
let animation: Animation | undefined;
|
|
35
|
-
const cancel = () => {
|
|
36
|
-
const active = animation;
|
|
37
|
-
animation = undefined;
|
|
38
|
-
try {
|
|
39
|
-
active?.cancel();
|
|
40
|
-
} catch {
|
|
41
|
-
/* An already detached effect cannot be released further. */
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
try {
|
|
45
|
-
animation = stage.animate(keyframes, {
|
|
46
|
-
duration: mobile ? Math.round(spec.duration * 0.62) : Math.round(spec.duration * 0.58),
|
|
47
|
-
// Strong ease-in: the outgoing document accelerates away. An exit
|
|
48
|
-
// is the one place ease-in is correct; entrances use ease-out.
|
|
49
|
-
easing: "cubic-bezier(0.4, 0, 1, 1)",
|
|
50
|
-
fill: "forwards",
|
|
51
|
-
});
|
|
52
|
-
signal.addEventListener("abort", cancel, { once: true });
|
|
53
|
-
await animation.finished;
|
|
54
|
-
// The stage node survives the swap, so this filled effect is what holds
|
|
55
|
-
// the incoming document hidden until its own cue starts.
|
|
56
|
-
} catch {
|
|
57
|
-
/* Cancellation and unsupported animation must not block a visit. */
|
|
58
|
-
}
|
|
59
|
-
return () => {
|
|
60
|
-
signal.removeEventListener("abort", cancel);
|
|
61
|
-
cancel();
|
|
62
|
-
};
|
|
63
|
-
}
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
let stopActive: (() => void) | undefined;
|
|
2
|
-
|
|
3
|
-
export function cancelPageScroll(): void {
|
|
4
|
-
stopActive?.();
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export interface PageGlide {
|
|
8
|
-
/** Settles when the page reaches the top, is interrupted, or is canceled. */
|
|
9
|
-
promise: Promise<void>;
|
|
10
|
-
/** Stop immediately at the current position without further frames. */
|
|
11
|
-
cancel: () => void;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** Bound the pre-swap return-to-top so a 6000px article does not add a second. */
|
|
15
|
-
const GLIDE_MIN_MS = 80;
|
|
16
|
-
const GLIDE_MAX_MS = 180;
|
|
17
|
-
const GLIDE_SAFETY_MS = 380;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Walk the current document back to the top before the container is replaced.
|
|
21
|
-
* Swup's own between-page reset is instant; animating it after the swap would
|
|
22
|
-
* make the new article start wherever the old page happened to be scrolled.
|
|
23
|
-
* Doing it on the outgoing document keeps the incoming page at its real top.
|
|
24
|
-
*/
|
|
25
|
-
export function glideToTop(): PageGlide {
|
|
26
|
-
const element = document.scrollingElement ?? document.documentElement;
|
|
27
|
-
const startTop = Math.max(0, element.scrollTop || window.scrollY || 0);
|
|
28
|
-
const startLeft = Math.max(0, element.scrollLeft || window.scrollX || 0);
|
|
29
|
-
let resolve = () => {};
|
|
30
|
-
const promise = new Promise<void>((done) => {
|
|
31
|
-
resolve = done;
|
|
32
|
-
});
|
|
33
|
-
if (
|
|
34
|
-
startTop < 1 ||
|
|
35
|
-
typeof requestAnimationFrame !== "function" ||
|
|
36
|
-
window.matchMedia?.("(prefers-reduced-motion: reduce)").matches ||
|
|
37
|
-
document.hidden
|
|
38
|
-
) {
|
|
39
|
-
resolve();
|
|
40
|
-
return { promise, cancel: resolve };
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Longer documents take proportionally longer, but the ceiling keeps the
|
|
44
|
-
// whole cue inside the same budget as the content fade.
|
|
45
|
-
const duration = Math.min(GLIDE_MAX_MS, GLIDE_MIN_MS + startTop / 20);
|
|
46
|
-
let frame: number | undefined;
|
|
47
|
-
let settled = false;
|
|
48
|
-
const release = () => {
|
|
49
|
-
window.removeEventListener("wheel", finish);
|
|
50
|
-
window.removeEventListener("touchstart", finish);
|
|
51
|
-
window.removeEventListener("keydown", finish);
|
|
52
|
-
clearTimeout(safety);
|
|
53
|
-
if (stopActive === finish) stopActive = undefined;
|
|
54
|
-
};
|
|
55
|
-
const finish = () => {
|
|
56
|
-
if (settled) return;
|
|
57
|
-
settled = true;
|
|
58
|
-
if (frame !== undefined) cancelAnimationFrame(frame);
|
|
59
|
-
release();
|
|
60
|
-
resolve();
|
|
61
|
-
};
|
|
62
|
-
// A wheel/touch/key press means the reader reclaimed the scroll; stop at
|
|
63
|
-
// the frame already rendered instead of fighting the gesture.
|
|
64
|
-
const safety = setTimeout(finish, GLIDE_SAFETY_MS);
|
|
65
|
-
stopActive = finish;
|
|
66
|
-
window.addEventListener("wheel", finish, { passive: true, once: true });
|
|
67
|
-
window.addEventListener("touchstart", finish, { passive: true, once: true });
|
|
68
|
-
window.addEventListener("keydown", finish, { once: true });
|
|
69
|
-
|
|
70
|
-
const begin = performance.now();
|
|
71
|
-
let frames = 0;
|
|
72
|
-
const step = (now: number) => {
|
|
73
|
-
if (settled) return;
|
|
74
|
-
// Frame ceiling: a host that invokes rAF synchronously must not turn
|
|
75
|
-
// this loop into unbounded recursion. Real browsers reach the safety
|
|
76
|
-
// timeout first; twenty frames cover the 320ms budget at 60Hz.
|
|
77
|
-
if (++frames > 240) {
|
|
78
|
-
finish();
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
const progress = Math.min(1, Math.max(0, (now - begin) / duration));
|
|
82
|
-
// Quadratic ease-out: reads as a scroll rather than a snap, and still
|
|
83
|
-
// spends most of its time near the top where the article begins.
|
|
84
|
-
const remaining = (1 - progress) ** 2;
|
|
85
|
-
element.scrollTop = Math.round(startTop * remaining);
|
|
86
|
-
element.scrollLeft = Math.round(startLeft * remaining);
|
|
87
|
-
if (progress < 1) frame = requestAnimationFrame(step);
|
|
88
|
-
else finish();
|
|
89
|
-
};
|
|
90
|
-
frame = requestAnimationFrame(step);
|
|
91
|
-
return { promise, cancel: finish };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/** Native scrolling with bounded completion and explicit listener cleanup. */
|
|
95
|
-
export function scrollPage(
|
|
96
|
-
element: Element,
|
|
97
|
-
top: number,
|
|
98
|
-
left: number,
|
|
99
|
-
animate: boolean,
|
|
100
|
-
start: () => void,
|
|
101
|
-
end: () => void,
|
|
102
|
-
): void {
|
|
103
|
-
stopActive?.();
|
|
104
|
-
start();
|
|
105
|
-
const smooth = animate && !window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
106
|
-
if (
|
|
107
|
-
!smooth ||
|
|
108
|
-
(Math.abs(element.scrollTop - top) < 1 && Math.abs(element.scrollLeft - left) < 1)
|
|
109
|
-
) {
|
|
110
|
-
element.scrollTo({ top, left, behavior: "instant" });
|
|
111
|
-
end();
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
const target =
|
|
115
|
-
element === document.documentElement || element === document.body ? window : element;
|
|
116
|
-
let finished = false;
|
|
117
|
-
const done = () => {
|
|
118
|
-
if (finished) return;
|
|
119
|
-
finished = true;
|
|
120
|
-
clearTimeout(timer);
|
|
121
|
-
target.removeEventListener("scrollend", done);
|
|
122
|
-
window.removeEventListener("wheel", interrupt);
|
|
123
|
-
window.removeEventListener("touchstart", interrupt);
|
|
124
|
-
window.removeEventListener("keydown", interrupt);
|
|
125
|
-
if (stopActive === interrupt) stopActive = undefined;
|
|
126
|
-
end();
|
|
127
|
-
};
|
|
128
|
-
const interrupt = () => {
|
|
129
|
-
element.scrollTo({ top: element.scrollTop, left: element.scrollLeft, behavior: "instant" });
|
|
130
|
-
done();
|
|
131
|
-
};
|
|
132
|
-
const timer = setTimeout(done, 1500);
|
|
133
|
-
stopActive = interrupt;
|
|
134
|
-
target.addEventListener("scrollend", done, { once: true });
|
|
135
|
-
window.addEventListener("wheel", interrupt, { passive: true, once: true });
|
|
136
|
-
window.addEventListener("touchstart", interrupt, { passive: true, once: true });
|
|
137
|
-
window.addEventListener("keydown", interrupt, { once: true });
|
|
138
|
-
element.scrollTo({ top, left, behavior: "smooth" });
|
|
139
|
-
}
|