Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New events for Astro's view transition API #9090

Merged
merged 14 commits into from
Nov 22, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/astro/client.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ declare module 'astro:transitions' {
type TransitionModule = typeof import('./dist/transitions/index.js');
export const slide: TransitionModule['slide'];
export const fade: TransitionModule['fade'];
export const createAnimationScope: TransitionModule['createAnimationScope'];

type ViewTransitionsModule = typeof import('./components/ViewTransitions.astro');
export const ViewTransitions: ViewTransitionsModule['default'];
Expand Down
1 change: 1 addition & 0 deletions packages/astro/components/ViewTransitions.astro
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const { fallback = 'animate', handleForms } = Astro.props;
import type { Options } from 'astro:transitions/client';
import { supportsViewTransitions, navigate } from 'astro:transitions/client';
// NOTE: import from `astro/prefetch` as `astro:prefetch` requires the `prefetch` config to be enabled
// @ts-ignore
import { init } from 'astro/prefetch';

type Fallback = 'none' | 'animate' | 'swap';
Expand Down
2 changes: 0 additions & 2 deletions packages/astro/e2e/view-transitions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,6 @@ test.describe('View Transitions', () => {
await page.goBack();
locator = page.locator('#click-one-again');
await expect(locator).toBeInViewport();

await page.goForward(); // prevent preemptive close of browser
});

test('Scroll position restored when transitioning back to fragment', async ({ page, astro }) => {
Expand Down
7 changes: 2 additions & 5 deletions packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,20 +82,17 @@ export interface TransitionAnimationPair {
new: TransitionAnimation | TransitionAnimation[];
}

export interface TransitionStrictDirectionalAnimations {
export interface TransitionDirectionalAnimations {
forwards: TransitionAnimationPair;
backwards: TransitionAnimationPair;
}

export type TransitionFreeDirectionalAnimations = Record<string, TransitionAnimationPair>;

export type TransitionAnimationValue =
| 'initial'
| 'slide'
| 'fade'
| 'none'
| TransitionStrictDirectionalAnimations
| TransitionFreeDirectionalAnimations;
| TransitionDirectionalAnimations;

// Allow users to extend this for astro-jsx.d.ts
// eslint-disable-next-line @typescript-eslint/no-empty-interface
Expand Down
37 changes: 30 additions & 7 deletions packages/astro/src/runtime/server/transition.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type {
SSRResult,
TransitionAnimation,
TransitionAnimationPair,
TransitionAnimationValue,
TransitionDirectionalAnimations,
} from '../../@types/astro.js';
import { fade, slide } from '../../transitions/index.js';
import { markHTMLString } from './escape.js';
Expand Down Expand Up @@ -34,6 +36,19 @@ const getAnimations = (name: TransitionAnimationValue) => {
if (typeof name === 'object') return name;
};

const addPairs = (
animations: TransitionDirectionalAnimations | Record<string, TransitionAnimationPair>,
stylesheet: ViewTransitionStyleSheet
) => {
for (const [direction, images] of Object.entries(animations) as Entries<typeof animations>) {
for (const [image, rules] of Object.entries(images) as Entries<
(typeof animations)[typeof direction]
>) {
stylesheet.addAnimationPair(direction, image, rules);
}
}
};

export function renderTransition(
result: SSRResult,
hash: string,
Expand All @@ -48,13 +63,7 @@ export function renderTransition(

const animations = getAnimations(animationName);
if (animations) {
for (const [direction, images] of Object.entries(animations) as Entries<typeof animations>) {
for (const [image, rules] of Object.entries(images) as Entries<
(typeof animations)[typeof direction]
>) {
sheet.addAnimationPair(direction, image, rules);
}
}
addPairs(animations, sheet);
} else if (animationName === 'none') {
sheet.addFallback('old', 'animation: none; mix-blend-mode: normal;');
sheet.addModern('old', 'animation: none; opacity: 0; mix-blend-mode: normal;');
Expand All @@ -65,6 +74,20 @@ export function renderTransition(
return scope;
}

export function createAnimationScope(
transitionName: string,
animations: Record<string, TransitionAnimationPair>
) {
const hash = Math.random().toString(36).slice(2, 8);
const scope = `astro-${hash}`;
const sheet = new ViewTransitionStyleSheet(scope, transitionName);

addPairs(animations, sheet);

const styles = `<style>${sheet.toString()}</style>`;
return { scope, styles };
}

class ViewTransitionStyleSheet {
private modern: string[] = [];
private fallback: string[] = [];
Expand Down
10 changes: 4 additions & 6 deletions packages/astro/src/transitions/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import type {
TransitionAnimationPair,
TransitionStrictDirectionalAnimations,
} from '../@types/astro.js';
import type { TransitionAnimationPair, TransitionDirectionalAnimations } from '../@types/astro.js';
export { createAnimationScope } from '../runtime/server/transition.js';

const EASE_IN_OUT_QUART = 'cubic-bezier(0.76, 0, 0.24, 1)';

export function slide({
duration,
}: {
duration?: string | number;
} = {}): TransitionStrictDirectionalAnimations {
} = {}): TransitionDirectionalAnimations {
return {
forwards: {
old: [
Expand Down Expand Up @@ -53,7 +51,7 @@ export function fade({
duration,
}: {
duration?: string | number;
} = {}): TransitionStrictDirectionalAnimations {
} = {}): TransitionDirectionalAnimations {
const anim = {
old: {
name: 'astroFadeOut',
Expand Down
19 changes: 11 additions & 8 deletions packages/astro/src/transitions/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ const samePage = (thisLocation: URL, otherLocation: URL) =>
thisLocation.pathname === otherLocation.pathname &&
thisLocation.search === otherLocation.search;

const allowIntraPageTransitions = () =>
inBrowser && !!document.querySelector('[name="astro-view-transitions-intra-page"]');

// When we traverse the history, the window.location is already set to the new location.
// This variable tells us where we came from
let originalLocation: URL;
Expand Down Expand Up @@ -399,6 +396,9 @@ async function updateDOM(
if (fallback === 'animate') {
await animate('old');
}
} else {
// that's what Chrome does
throw new DOMException('Transition was skipped');
}

const swapEvent = await doSwap(preparationEvent, viewTransition!, defaultSwap);
Expand All @@ -422,6 +422,12 @@ async function transition(
: options.history === 'replace'
? 'replace'
: 'push';

if (samePage(from, to) && !options.formData /* not yet: && to.hash*/) {
moveToLocation(to, from, options, historyState);
return;
}

const prepEvent = await doPreparation(
from,
to,
Expand Down Expand Up @@ -527,9 +533,6 @@ async function transition(
},
};
}
if (samePage(prepEvent.from, prepEvent.to) && !allowIntraPageTransitions) {
viewTransition.skipTransition();
}

viewTransition.ready.then(async () => {
await runScripts();
Expand All @@ -545,7 +548,7 @@ async function transition(

let navigateOnServerWarned = false;

export function navigate(href: string, options?: Options) {
export async function navigate(href: string, options?: Options) {
if (inBrowser === false) {
if (!navigateOnServerWarned) {
// instantiate an error for the stacktrace to show to user.
Expand All @@ -565,7 +568,7 @@ export function navigate(href: string, options?: Options) {
location.href = href;
return;
}
transition('forward', originalLocation, new URL(href, location.href), options ?? {});
await transition('forward', originalLocation, new URL(href, location.href), options ?? {});
}

function onPopState(ev: PopStateEvent) {
Expand Down