@@ -19,7 +19,9 @@ const { fallback = 'animate' } = Astro.props as Props;
19
19
};
20
20
type Events = 'astro:page-load' | 'astro:after-swap';
21
21
22
- const persistState = (state: State) => history.replaceState(state, '');
22
+ // only update history entries that are managed by us
23
+ // leave other entries alone and do not accidently add state.
24
+ const persistState = (state: State) => history.state && history.replaceState(state, '');
23
25
const supportsViewTransitions = !!document.startViewTransition;
24
26
const transitionEnabledOnThisPage = () =>
25
27
!!document.querySelector('[name="astro-view-transitions-enabled"]');
@@ -32,11 +34,13 @@ const { fallback = 'animate' } = Astro.props as Props;
32
34
// can use that to determine popstate if going forward or back.
33
35
let currentHistoryIndex = 0;
34
36
if (history.state) {
35
- // we reloaded a page with history state (e.g. back button or browser reload)
37
+ // we reloaded a page with history state
38
+ // (e.g. history navigation from non-transition page or browser reload)
36
39
currentHistoryIndex = history.state.index;
37
40
scrollTo({ left: 0, top: history.state.scrollY });
41
+ } else if (transitionEnabledOnThisPage()) {
42
+ history.replaceState({index: currentHistoryIndex, scrollY}, '');
38
43
}
39
-
40
44
const throttle = (cb: (...args: any[]) => any, delay: number) => {
41
45
let wait = false;
42
46
// During the waiting time additional events are lost.
@@ -109,9 +113,7 @@ const { fallback = 'animate' } = Astro.props as Props;
109
113
110
114
const parser = new DOMParser();
111
115
112
- async function updateDOM(html: string, state?: State, fallback?: Fallback) {
113
- const doc = parser.parseFromString(html, 'text/html');
114
-
116
+ async function updateDOM(doc: Document, loc: URL, state?: State, fallback?: Fallback) {
115
117
// Check for a head element that should persist, either because it has the data
116
118
// attribute or is a link el.
117
119
const persistedHeadElement = (el: Element): Element | null => {
@@ -189,19 +191,21 @@ const { fallback = 'animate' } = Astro.props as Props;
189
191
// Chromium based browsers (Chrome, Edge, Opera, ...)
190
192
scrollTo({ left: 0, top: 0, behavior: 'instant' });
191
193
192
- if (state?.scrollY === 0 && location.hash) {
193
- const id = decodeURIComponent(location.hash.slice(1));
194
+ let initialScrollY = 0;
195
+ if (!state && loc.hash) {
196
+ const id = decodeURIComponent(loc.hash.slice(1));
194
197
const elem = document.getElementById(id);
195
198
// prefer scrollIntoView() over scrollTo() because it takes scroll-padding into account
196
- if (elem) {
197
- state.scrollY = elem.offsetTop;
198
- persistState(state); // first guess, later updated by scroll handler
199
- elem.scrollIntoView(); // for Firefox, this should better be {behavior: 'instant'}
200
- }
199
+ elem && (initialScrollY = elem.offsetTop) && elem.scrollIntoView();
201
200
} else if (state && state.scrollY !== 0) {
202
201
scrollTo(0, state.scrollY); // usings default scrollBehavior
203
202
}
204
-
203
+ !state &&
204
+ history.pushState(
205
+ { index: ++currentHistoryIndex, scrollY: initialScrollY },
206
+ '',
207
+ loc.href
208
+ );
205
209
triggerEvent('astro:after-swap');
206
210
};
207
211
@@ -247,19 +251,26 @@ const { fallback = 'animate' } = Astro.props as Props;
247
251
}
248
252
}
249
253
250
- async function navigate(dir: Direction, href: string , state?: State) {
254
+ async function navigate(dir: Direction, loc: URL , state?: State) {
251
255
let finished: Promise<void>;
256
+ const href=loc.href;
252
257
const { html, ok } = await getHTML(href);
253
258
// If there is a problem fetching the new page, just do an MPA navigation to it.
254
259
if (!ok) {
255
260
location.href = href;
256
261
return;
257
262
}
263
+ const doc = parser.parseFromString(html, 'text/html');
264
+ if (!doc.querySelector('[name="astro-view-transitions-enabled"]')) {
265
+ location.href = href;
266
+ return;
267
+ }
268
+
258
269
document.documentElement.dataset.astroTransition = dir;
259
270
if (supportsViewTransitions) {
260
- finished = document.startViewTransition(() => updateDOM(html , state)).finished;
271
+ finished = document.startViewTransition(() => updateDOM(doc, loc , state)).finished;
261
272
} else {
262
- finished = updateDOM(html , state, getFallback());
273
+ finished = updateDOM(doc, loc , state, getFallback());
263
274
}
264
275
try {
265
276
await finished;
@@ -311,11 +322,11 @@ const { fallback = 'animate' } = Astro.props as Props;
311
322
ev.shiftKey || // new window
312
323
ev.defaultPrevented ||
313
324
!transitionEnabledOnThisPage()
314
- )
325
+ ) {
315
326
// No page transitions in these cases,
316
327
// Let the browser standard action handle this
317
328
return;
318
-
329
+ }
319
330
// We do not need to handle same page links because there are no page transitions
320
331
// Same page means same path and same query params (but different hash)
321
332
if (location.pathname === link.pathname && location.search === link.search) {
@@ -341,10 +352,8 @@ const { fallback = 'animate' } = Astro.props as Props;
341
352
342
353
// these are the cases we will handle: same origin, different page
343
354
ev.preventDefault();
344
- navigate('forward', link.href, { index: ++currentHistoryIndex, scrollY: 0 });
345
- const newState: State = { index: currentHistoryIndex, scrollY };
346
- persistState({ index: currentHistoryIndex - 1, scrollY });
347
- history.pushState(newState, '', link.href);
355
+ persistState({ index: currentHistoryIndex, scrollY });
356
+ navigate('forward', new URL(link.href));
348
357
});
349
358
350
359
addEventListener('popstate', (ev) => {
@@ -374,11 +383,11 @@ const { fallback = 'animate' } = Astro.props as Props;
374
383
history.scrollRestoration = 'manual';
375
384
}
376
385
377
- const state: State | undefined = history.state;
378
- const nextIndex = state? .index ?? currentHistoryIndex + 1 ;
386
+ const state: State = history.state;
387
+ const nextIndex = state.index;
379
388
const direction: Direction = nextIndex > currentHistoryIndex ? 'forward' : 'back';
380
- navigate(direction, location.href, state);
381
389
currentHistoryIndex = nextIndex;
390
+ navigate(direction, new URL(location.href), state);
382
391
});
383
392
384
393
['mouseenter', 'touchstart', 'focus'].forEach((evName) => {
@@ -402,13 +411,10 @@ const { fallback = 'animate' } = Astro.props as Props;
402
411
addEventListener('load', onPageLoad);
403
412
// There's not a good way to record scroll position before a back button.
404
413
// So the way we do it is by listening to scrollend if supported, and if not continuously record the scroll position.
405
- const updateState = () => {
406
- // only update history entries that are managed by us
407
- // leave other entries alone and do not accidently add state.
408
- if (history.state) {
409
- persistState({ ...history.state, scrollY });
410
- }
411
- };
414
+ const updateState = () => {
415
+ persistState({ ...history.state, scrollY });
416
+ }
417
+
412
418
if ('onscrollend' in window) addEventListener('scrollend', updateState);
413
419
else addEventListener('scroll', throttle(updateState, 300));
414
420
}
0 commit comments