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

proposal for better support for custom swap functions #10908

Merged
merged 12 commits into from
May 8, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
---
import Layout from "../components/Layout.astro"
---
<Layout>
<h1 id="name">Chaining</h1>
<a id="click" href="/one">click</a>
</Layout>

<script>
import { swap } from "../../node_modules/astro/dist/transitions/swap-functions";

const t = "[test]"

document.addEventListener('astro:before-swap', (e) => {
const originalSwap = e.swap;
e.swap = () => {
console.log(t, "99");
originalSwap();
console.log(t, "00");
}
})

document.addEventListener('astro:before-swap', (e) => {
e.swap = () => {
console.log(t, "3");
swap(e.newDocument);
console.log(t, "2");
}
})

document.addEventListener('astro:before-swap', (e) => {
const originalSwap = e.swap;
e.swap = () => {
console.log(t, "4");
originalSwap();
console.log(t, "1");
}
})

document.addEventListener('astro:before-swap', (e) => {
const originalSwap = e.swap;
e.swap = () => {
console.log(t, "5");
originalSwap();
console.log(t, "0");
}
})

</script>
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ViewTransitions } from 'astro:transitions';

// For the test fixture, we import the script but we don't use the <ViewTransitions /> component
// While this seems to be some strange mistake,
// it might be realistic, e.g. in a configurable CommentHead component
// it might be realistic, e.g. in a configurable CommonHead component

interface Props {
transitions?: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
import Layout from '../components/Layout.astro';
---
<Layout>
<p id="name">Keep Style</p>
<a id="click" href="/keep-two">go to next page</a>
</Layout>
<script>

import { deselectScripts, saveFocus, swapBodyElement, swapHeadElements, swapRootAttributes } from '../../node_modules/astro/dist/transitions/swap-functions'

document.addEventListener('astro:before-swap', (e) => {
e.swap = () => keepStyle(e.newDocument)
});

function keepStyle(doc: Document) {
deselectScripts(doc);
swapRootAttributes(doc);
{
const dynamicStyle = document.head.querySelector('style:not(:empty)');
swapHeadElements(doc);
dynamicStyle && document.head.insertAdjacentElement('afterbegin', dynamicStyle);
}
const restoreFocusFunction = saveFocus();
swapBodyElement(doc.body, document.body)
restoreFocusFunction();
}
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
import Layout from '../components/Layout.astro';
---
<Layout>
<p id="name">Keep Theme</p>
<a id="click" href="/keep-two">go to next page</a>
</Layout>
<script>
import { deselectScripts, saveFocus, swapBodyElement, swapHeadElements, swapRootAttributes } from '../../node_modules/astro/dist/transitions/swap-functions'

function keepTheme(doc:Document) {
deselectScripts(doc);
{
const theme = document.documentElement.getAttribute('data-theme')!;
swapRootAttributes(doc);
document.documentElement.setAttribute('data-theme', theme);
}
swapHeadElements(doc);
const restoreFocusFunction = saveFocus();
swapBodyElement(doc.body, document.body)
restoreFocusFunction();
}

document.addEventListener('astro:before-swap', (e) => {
e.swap = () => keepTheme(e.newDocument);
});
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
import Layout from '../components/Layout.astro';
---
<Layout>
<section>
<p id="name">Keep 2</p>
<p>This is the main section from page 2</p>
</section>
</Layout>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
import Layout from '../components/Layout.astro';
---
<Layout>
<section>
<p id="name">Replace Main Section</p>
<p>This is the main section</p>
</section>
<a id="click" href="/keep-two">go to next page</a>
</Layout>
<script>
import { deselectScripts, saveFocus, swapBodyElement, swapHeadElements, swapRootAttributes } from "../../node_modules/astro/dist/transitions/swap-functions"

document.addEventListener('astro:before-swap', (e) => {
e.swap = () => replaceMain(e.newDocument)
});

function replaceMain(doc:Document){
deselectScripts(doc);
swapRootAttributes(doc);
swapHeadElements(doc);
const restoreFocusFunction = saveFocus();
{
const newMain = doc.body.querySelector('main section');
const oldMain = document.body.querySelector('main section');
if (newMain && oldMain) {
swapBodyElement(newMain, oldMain);
} else {
swapBodyElement(doc.body, document.body);
}
}
restoreFocusFunction();
}
</script>
69 changes: 69 additions & 0 deletions packages/astro/e2e/view-transitions.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1444,6 +1444,75 @@ test.describe('View Transitions', () => {
expect(text).toBe('true true');
});


test('it should be easy to define a data-theme preserving swap function', async ({
page,
astro,
}) => {
await page.goto(astro.resolveUrl('/keep-theme-one'));
await expect(page.locator('#name'), 'should have content').toHaveText('Keep Theme');
await page.$eval(':root', (element) => element.setAttribute('data-theme', 'purple'));

await page.click('#click');
await expect(page.locator('#name'), 'should have content').toHaveText('Keep 2');

const attributeValue = await page.$eval(
':root',
(element, attributeName) => element.getAttribute(attributeName),
'data-theme'
);
expect(attributeValue).toBe('purple');
});

test('it should be easy to define a swap function that preserves a dynamically generated style sheet', async ({
page,
astro,
}) => {
await page.goto(astro.resolveUrl('/keep-style-one'));
await expect(page.locator('#name'), 'should have content').toHaveText('Keep Style');
await page.evaluate(() => {
const style = document.createElement('style');
style.textContent = 'body { background-color: purple; }';
document.head.insertAdjacentElement('afterbegin', style);
});

await page.click('#click');
await expect(page.locator('#name'), 'should have content').toHaveText('Keep 2');

const styleElement = await page.$('head > style');
const styleContent = await page.evaluate((style) => style.innerHTML, styleElement);
expect(styleContent).toBe('body { background-color: purple; }');
});

test('it should be easy to define a swap function that only swaps the main area', async ({
page,
astro,
}) => {
await page.goto(astro.resolveUrl('/replace-main-one'));
await expect(page.locator('#name'), 'should have content').toHaveText('Replace Main Section');

await page.click('#click');
// name inside <main> should have changed
await expect(page.locator('#name'), 'should have content').toHaveText('Keep 2');

// link outside <main> should still be there
const link = await page.$('#click');
expect(link).toBeTruthy();
});

test('chaining should execute in the expected order', async ({ page, astro }) => {
let lines = [];
page.on('console', (msg) => {
msg.text().startsWith('[test]') && lines.push(msg.text().slice('[test]'.length + 1));
});

await page.goto(astro.resolveUrl('/chaining'));
await expect(page.locator('#name'), 'should have content').toHaveText('Chaining');
await page.click('#click');
await expect(page.locator('#one'), 'should have content').toHaveText('Page 1');
expect(lines.join('..')).toBe('5..4..3..2..1..0');
});

test('Navigation should be interruptible', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/abort'));
// implemented in /abort:
Expand Down
9 changes: 4 additions & 5 deletions packages/astro/src/transitions/events.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { swap } from './swap-functions.js';
import { updateScrollPosition } from './router.js';
import type { Direction, NavigationTypeString } from './types.js';

Expand Down Expand Up @@ -118,8 +119,7 @@ export class TransitionBeforeSwapEvent extends BeforeEvent {

constructor(
afterPreparation: BeforeEvent,
viewTransition: ViewTransition,
swap: (event: TransitionBeforeSwapEvent) => void
viewTransition: ViewTransition
) {
super(
TRANSITION_BEFORE_SWAP,
Expand All @@ -135,7 +135,7 @@ export class TransitionBeforeSwapEvent extends BeforeEvent {
);
this.direction = afterPreparation.direction;
this.viewTransition = viewTransition;
this.swap = swap.bind(this, this);
this.swap = () => swap(this.newDocument);

Object.defineProperties(this, {
direction: { enumerable: true },
Expand Down Expand Up @@ -184,9 +184,8 @@ export async function doPreparation(
export function doSwap(
afterPreparation: BeforeEvent,
viewTransition: ViewTransition,
defaultSwap: (event: TransitionBeforeSwapEvent) => void
) {
const event = new TransitionBeforeSwapEvent(afterPreparation, viewTransition, defaultSwap);
const event = new TransitionBeforeSwapEvent(afterPreparation, viewTransition);
document.dispatchEvent(event);
event.swap();
return event;
Expand Down
Loading
Loading